"""Unit tests for HTTPClient."""

import unittest
from unittest.mock import Mock, patch
import requests
from src.blackant.http.client import HTTPClient, HTTPConnectionError
from src.blackant.auth.tokens import AuthTokenStore
from src.blackant.auth.request_id import RequestIdStore


class TestHTTPClient(unittest.TestCase):
    """Test cases for HTTPClient."""

    def setUp(self):
        """Set up test fixtures."""
        # Mock logging to avoid dependency issues in tests
        with patch('src.blackant.http.client.get_logger'), \
                patch('src.blackant.http.client.set_request_id'), \
                patch('src.blackant.http.client.clear_request_id'):
            self.client = HTTPClient("https://api.example.com")

    def test_initialization(self):
        """Test HTTPClient initialization."""
        self.assertEqual(self.client.base_url, "https://api.example.com")
        self.assertIsInstance(self.client.auth_token_store, AuthTokenStore)
        self.assertIsInstance(self.client.request_id_store, RequestIdStore)

    def test_base_url_stripping(self):
        """Test that base URL trailing slash is stripped."""
        with patch('src.blackant.http.client.get_logger'), \
                patch('src.blackant.http.client.set_request_id'), \
                patch('src.blackant.http.client.clear_request_id'):
            client = HTTPClient("https://api.example.com/")
        self.assertEqual(client.base_url, "https://api.example.com")

    @patch('src.blackant.http.client.get_logger')
    @patch('src.blackant.http.client.set_request_id')
    @patch('src.blackant.http.client.clear_request_id')
    def test_send_request_anonymous(self, mock_clear, mock_set, mock_logger):
        """Test anonymous request (no authentication)."""
        # Mock the session and response
        mock_response = Mock()
        mock_response.status_code = 200
        self.client.session.send = Mock(return_value=mock_response)

        # Let generate_id work normally - no mocking
        response = self.client.send_request("/test", "GET", anonymous=True)

        self.assertEqual(response, mock_response)
        # set_request_id should be called with some request ID
        self.assertEqual(mock_set.call_count, 1)
        mock_clear.assert_called_once()

    @patch('src.blackant.http.client.get_logger')
    @patch('src.blackant.http.client.set_request_id')
    @patch('src.blackant.http.client.clear_request_id')
    def test_send_request_with_user_token(self, mock_clear, mock_set, mock_logger):
        """Test request with user token authentication."""
        # Set up token
        self.client.auth_token_store.user_token = "user_token_123"

        # Mock the session and response
        mock_response = Mock()
        mock_response.status_code = 200
        self.client.session.send = Mock(return_value=mock_response)

        response = self.client.send_request("/test", "GET")

        # Verify Authorization header was set
        self.client.session.send.assert_called_once()
        call_args = self.client.session.send.call_args[0]
        prepared_request = call_args[0]
        self.assertEqual(prepared_request.headers["Authorization"], "Bearer user_token_123")

    @patch('src.blackant.http.client.get_logger')
    @patch('src.blackant.http.client.set_request_id')
    @patch('src.blackant.http.client.clear_request_id')
    def test_send_request_with_admin_token(self, mock_clear, mock_set, mock_logger):
        """Test request with admin token authentication."""
        # Set up token
        self.client.auth_token_store.admin_token = "admin_token_456"

        # Mock the session and response
        mock_response = Mock()
        mock_response.status_code = 200
        self.client.session.send = Mock(return_value=mock_response)

        response = self.client.send_request("/test", "GET", is_admin=True)

        # Verify Authorization header was set with admin token
        self.client.session.send.assert_called_once()
        call_args = self.client.session.send.call_args[0]
        prepared_request = call_args[0]
        self.assertEqual(prepared_request.headers["Authorization"], "Bearer admin_token_456")

    @patch('src.blackant.http.client.get_logger')
    @patch('src.blackant.http.client.set_request_id')
    @patch('src.blackant.http.client.clear_request_id')
    def test_send_request_with_custom_token(self, mock_clear, mock_set, mock_logger):
        """Test request with custom token parameter."""
        # Mock the session and response
        mock_response = Mock()
        mock_response.status_code = 200
        self.client.session.send = Mock(return_value=mock_response)

        response = self.client.send_request("/test", "GET", token="custom_token_789")

        # Verify Authorization header was set with custom token
        self.client.session.send.assert_called_once()
        call_args = self.client.session.send.call_args[0]
        prepared_request = call_args[0]
        self.assertEqual(prepared_request.headers["Authorization"], "Bearer custom_token_789")

    @patch('src.blackant.http.client.get_logger')
    @patch('src.blackant.http.client.set_request_id')
    @patch('src.blackant.http.client.clear_request_id')
    def test_send_request_connection_error(self, mock_clear, mock_set, mock_logger):
        """Test connection error handling."""
        # Mock connection error
        self.client.session.send = Mock(side_effect=requests.exceptions.ConnectionError("Connection failed"))

        with self.assertRaises(HTTPConnectionError):
            self.client.send_request("/test", "GET")

        # Verify cleanup is called even on error
        mock_clear.assert_called_once()

    @patch('src.blackant.http.client.get_logger')
    @patch('src.blackant.http.client.set_request_id')
    @patch('src.blackant.http.client.clear_request_id')
    def test_send_request_timeout_error(self, mock_clear, mock_set, mock_logger):
        """Test timeout error handling."""
        # Mock timeout error
        self.client.session.send = Mock(side_effect=requests.exceptions.Timeout("Request timed out"))

        with self.assertRaises(HTTPConnectionError):
            self.client.send_request("/test", "GET")

        # Verify cleanup is called even on error
        mock_clear.assert_called_once()

    def test_url_construction(self):
        """Test URL construction with different endpoint formats."""
        # Mock the session to avoid actual requests
        self.client.session.send = Mock(return_value=Mock())

        with patch('src.blackant.http.client.set_request_id'), \
                patch('src.blackant.http.client.clear_request_id'):
            # Test with leading slash
            self.client.send_request("/api/v1/test", "GET", anonymous=True)
            call_args = self.client.session.send.call_args[0]
            prepared_request = call_args[0]
            self.assertEqual(prepared_request.url, "https://api.example.com/api/v1/test")

            # Test without leading slash
            self.client.send_request("api/v1/test", "GET", anonymous=True)
            call_args = self.client.session.send.call_args[0]
            prepared_request = call_args[0]
            self.assertEqual(prepared_request.url, "https://api.example.com/api/v1/test")

    def test_request_id_header_added(self):
        """Test that X-Request-ID header is added to requests."""
        mock_response = Mock()
        mock_response.status_code = 200
        self.client.session.send = Mock(return_value=mock_response)

        with patch('src.blackant.http.client.set_request_id'), \
                patch('src.blackant.http.client.clear_request_id'):
            self.client.send_request("/test", "GET", anonymous=True)

            # Verify request has X-Request-ID header
            call_args = self.client.session.send.call_args[0]
            prepared_request = call_args[0]
            self.assertIn("X-Request-ID", prepared_request.headers)
            # Verify it's not empty
            self.assertIsNotNone(prepared_request.headers["X-Request-ID"])
            self.assertNotEqual(prepared_request.headers["X-Request-ID"], "")


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