# Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
# OpenOLC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         `http://license.coscl.org.cn/MulanPSL2`
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

import unittest
from unittest.mock import Mock, patch
from requests.exceptions import RequestException, Timeout

from olc.remote.http_client import (
    HttpClient,
    HttpMethod,
    Request,
    Response,
    HttpException,
    LoggingInterceptor,
)


class TestRequest(unittest.TestCase):
    def test_request_repr(self):
        request = Request(method=HttpMethod.GET, url="http://example.com")
        self.assertIn("<request: HttpMethod.GET http://example.com>", repr(request))

    def test_request_str(self):
        request = Request(method=HttpMethod.POST, url="http://example.com/api")
        self.assertEqual(str(request), repr(request))


class TestResponse(unittest.TestCase):
    def test_response_ok(self):
        response = Response(
            status_code=200,
            url="http://example.com",
            headers={},
            content=b'{"key": "value"}',
            text='{"key": "value"}',
            elapsed=0.1,
        )
        self.assertTrue(response.ok())

    def test_response_not_ok(self):
        response = Response(
            status_code=404,
            url="http://example.com",
            headers={},
            content=b"Not Found",
            text="Not Found",
            elapsed=0.1,
        )
        self.assertFalse(response.ok())

    def test_response_json(self):
        response = Response(
            status_code=200,
            url="http://example.com",
            headers={},
            content=b'{"key": "value"}',
            text='{"key": "value"}',
            elapsed=0.1,
        )
        self.assertEqual(response.json(), {"key": "value"})

    def test_response_json_invalid(self):
        response = Response(
            status_code=200,
            url="http://example.com",
            headers={},
            content=b"not json",
            text="not json",
            elapsed=0.1,
        )
        with self.assertRaises(ValueError):
            response.json()

    def test_response_repr(self):
        response = Response(
            status_code=200,
            url="http://example.com",
            headers={},
            content=b"",
            text="",
            elapsed=0.5,
        )
        self.assertIn("<response: 200 http://example.com>", repr(response))


class TestHttpException(unittest.TestCase):
    def test_http_exception_with_code(self):
        exc = HttpException("test error", code=500)
        self.assertEqual(exc.message, "test error")
        self.assertEqual(exc.code, 500)

    def test_http_exception_repr(self):
        exc = HttpException("error", code=404)
        self.assertIn("HttpException(code=404, message=error)", repr(exc))


class TestHttpClient(unittest.TestCase):
    def test_build_url_with_base_url(self):
        client = HttpClient(base_url="http://api.example.com")
        self.assertEqual(client._build_url("/users"), "http://api.example.com/users")
        self.assertEqual(client._build_url("users"), "http://api.example.com/users")
        self.assertEqual(client._build_url("http://other.com/test"), "http://other.com/test")

    def test_build_url_without_base_url(self):
        client = HttpClient()
        self.assertEqual(client._build_url("http://example.com"), "http://example.com")

    def test_build_url_trailing_slash(self):
        client = HttpClient(base_url="http://api.example.com/")
        self.assertEqual(client._build_url("/users"), "http://api.example.com/users")

    @patch("olc.remote.http_client.requests.request")
    def test_get_request(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.url = "http://example.com/test"
        mock_response.headers = {"Content-Type": "application/json"}
        mock_response.content = b'{"result": "success"}'
        mock_response.text = '{"result": "success"}'

        mock_request.return_value = mock_response

        client = HttpClient()
        response = client.get("http://example.com/test")

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.url, "http://example.com/test")
        self.assertTrue(response.ok())
        mock_request.assert_called_once_with(
            method="GET",
            url="http://example.com/test",
            headers=None,
            params=None,
            data=None,
            json=None,
            timeout=(10, 30),
        )

    @patch("olc.remote.http_client.requests.request")
    def test_post_request(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 201
        mock_response.url = "http://example.com/create"
        mock_response.headers = {"Content-Type": "application/json"}
        mock_response.content = b'{"id": 1}'
        mock_response.text = '{"id": 1}'

        mock_request.return_value = mock_response

        client = HttpClient()
        response = client.post(
            "http://example.com/create",
            json_data={"name": "test"},
            headers={"Authorization": "Bearer token"},
        )

        self.assertEqual(response.status_code, 201)
        mock_request.assert_called_once()
        call_kwargs = mock_request.call_args[1]
        self.assertEqual(call_kwargs["method"], "POST")
        self.assertEqual(call_kwargs["json"], {"name": "test"})
        self.assertEqual(call_kwargs["headers"], {"Authorization": "Bearer token"})

    @patch("olc.remote.http_client.requests.request")
    def test_put_request(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.url = "http://example.com/update/1"
        mock_response.headers = {}
        mock_response.content = b'{"updated": true}'
        mock_response.text = '{"updated": true}'

        mock_request.return_value = mock_response

        client = HttpClient()
        response = client.put(
            "http://example.com/update/1",
            json_data={"name": "updated"},
        )

        self.assertEqual(response.status_code, 200)
        mock_request.assert_called_once()
        call_kwargs = mock_request.call_args[1]
        self.assertEqual(call_kwargs["method"], "PUT")

    @patch("olc.remote.http_client.requests.request")
    def test_delete_request(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 204
        mock_response.url = "http://example.com/delete/1"
        mock_response.headers = {}
        mock_response.content = b""
        mock_response.text = ""

        mock_request.return_value = mock_response

        client = HttpClient()
        response = client.delete("http://example.com/delete/1")

        self.assertEqual(response.status_code, 204)
        mock_request.assert_called_once()
        call_kwargs = mock_request.call_args[1]
        self.assertEqual(call_kwargs["method"], "DELETE")

    @patch("olc.remote.http_client.requests.request")
    def test_get_json_success(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.url = "http://example.com/data"
        mock_response.headers = {"Content-Type": "application/json"}
        mock_response.content = b'{"data": "value"}'
        mock_response.text = '{"data": "value"}'

        mock_request.return_value = mock_response

        client = HttpClient()
        result = client.get_json("http://example.com/data")

        self.assertEqual(result, {"data": "value"})

    @patch("olc.remote.http_client.requests.request")
    def test_get_json_failure(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 404
        mock_response.url = "http://example.com/data"
        mock_response.headers = {"Content-Type": "application/json"}
        mock_response.content = b'{"error": "Not Found"}'
        mock_response.text = '{"error": "Not Found"}'

        mock_request.return_value = mock_response

        client = HttpClient()
        with self.assertRaises(HttpException) as context:
            client.get_json("http://example.com/data")
        self.assertIn("get json failed", str(context.exception))
        self.assertEqual(context.exception.code, 404)

    @patch("olc.remote.http_client.requests.request")
    def test_get_json_invalid_json(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.url = "http://example.com/data"
        mock_response.headers = {}
        mock_response.content = b"Not a JSON"
        mock_response.text = "Not a JSON"

        mock_request.return_value = mock_response

        client = HttpClient()
        with self.assertRaises(HttpException) as context:
            client.get_json("http://example.com/data")
        self.assertIn("response is not valid json", str(context.exception))

    @patch("olc.remote.http_client.requests.request")
    def test_post_json_success(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.url = "http://example.com/post"
        mock_response.headers = {"Content-Type": "application/json"}
        mock_response.content = b'{"result": "success"}'
        mock_response.text = '{"result": "success"}'

        mock_request.return_value = mock_response

        client = HttpClient()
        result = client.post_json("http://example.com/post", json_data={"key": "value"})

        self.assertEqual(result, {"result": "success"})

    @patch("olc.remote.http_client.requests.request")
    def test_post_json_failure(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 500
        mock_response.url = "http://example.com/post"
        mock_response.headers = {"Content-Type": "application/json"}
        mock_response.content = b'{"error": "Internal Server Error"}'
        mock_response.text = '{"error": "Internal Server Error"}'

        mock_request.return_value = mock_response

        client = HttpClient()
        with self.assertRaises(HttpException) as context:
            client.post_json("http://example.com/post", json_data={"key": "value"})
        self.assertIn("post json failed", str(context.exception))
        self.assertEqual(context.exception.code, 500)

    @patch("olc.remote.http_client.requests.request")
    def test_post_json_invalid_json(self, mock_request):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.url = "http://example.com/post"
        mock_response.headers = {}
        mock_response.content = b"Not a JSON"
        mock_response.text = "Not a JSON"

        mock_request.return_value = mock_response

        client = HttpClient()
        with self.assertRaises(HttpException) as context:
            client.post_json("http://example.com/post", json_data={"key": "value"})
        self.assertIn("response is not valid json", str(context.exception))

    @patch("olc.remote.http_client.requests.request")
    def test_request_timeout(self, mock_request):
        mock_request.side_effect = Timeout("Connection timed out")

        client = HttpClient(timeout=(1, 1))
        with self.assertRaises(HttpException) as context:
            client.get("http://example.com/test")
        self.assertIn("request failed", str(context.exception))

    @patch("olc.remote.http_client.requests.request")
    def test_request_network_error(self, mock_request):
        mock_request.side_effect = RequestException("Network error")

        client = HttpClient()
        with self.assertRaises(HttpException):
            client.get("http://example.com/test")

    @patch("olc.remote.http_client.requests.request")
    def test_retry_on_server_error(self, mock_request):
        mock_response_500 = Mock()
        mock_response_500.status_code = 500
        mock_response_500.url = "http://example.com/test"
        mock_response_500.headers = {}
        mock_response_500.content = b"Internal Server Error"
        mock_response_500.text = "Internal Server Error"

        mock_response_200 = Mock()
        mock_response_200.status_code = 200
        mock_response_200.url = "http://example.com/test"
        mock_response_200.headers = {}
        mock_response_200.content = b'{"success": true}'
        mock_response_200.text = '{"success": true}'

        mock_request.side_effect = [mock_response_500, mock_response_200]

        client = HttpClient(max_retries=1)
        response = client.get("http://example.com/test")

        self.assertEqual(response.status_code, 200)
        self.assertEqual(mock_request.call_count, 2)

    @patch("olc.remote.http_client.requests.request")
    def test_retry_for_post(self, mock_request):
        mock_response_500 = Mock()
        mock_response_500.status_code = 500
        mock_response_500.url = "http://example.com/test"
        mock_response_500.headers = {}
        mock_response_500.content = b"Internal Server Error"
        mock_response_500.text = "Internal Server Error"

        mock_request.return_value = mock_response_500

        client = HttpClient(max_retries=3)
        response = client.post("http://example.com/test")

        self.assertEqual(response.status_code, 500)
        self.assertEqual(mock_request.call_count, 4)

    def test_add_interceptor(self):
        client = HttpClient(interceptors=[])
        interceptor = LoggingInterceptor()
        client.add_interceptor(interceptor)
        self.assertEqual(len(client.interceptors), 1)
        self.assertEqual(client.interceptors[0], interceptor)

    def test_add_none_interceptor(self):
        client = HttpClient(interceptors=[])
        client.add_interceptor(None)
        self.assertEqual(len(client.interceptors), 0)


if __name__ == '__main__':
    unittest.main()