"""
Unit and integration tests for custom SSL certificate support.

Tests cover:
- Configuration layer (SettingsManager)
- Session layer (Session singleton)
- API Service layer (ApiService, GEAIApiService)
- Client layer (BaseClient)
- Integration flows
- Mock verification of requests library calls

Run with:
    python -m unittest pygeai.tests.core.common.test_ssl_cert
    python -m unittest pygeai.tests.core.common.test_ssl_cert.TestConfigurationLayer
    python -m unittest pygeai.tests.core.common.test_ssl_cert.TestConfigurationLayer.test_set_and_get_ssl_cert_path
"""

import os
from unittest import TestCase
from unittest.mock import patch, MagicMock, mock_open
from pathlib import Path
from pygeai.core.common.config import get_settings, reset_settings, SettingsManager
from pygeai.core.base.session import Session, get_session, reset_session
from pygeai.core.services.rest import ApiService, GEAIApiService
from pygeai.core.base.clients import BaseClient


class TestConfigurationLayer(TestCase):
    """Test SSL certificate configuration storage and retrieval."""
    
    def setUp(self):
        """Mock credentials file to avoid touching real file."""
        self.creds_content = """
[default]
geai_api_key = test_api_key
geai_api_base_url = https://api.test.com
geai_api_eval_url = https://eval.test.com

[test-alias]
geai_api_key = test-key
geai_api_base_url = https://test.example.com
"""
        # Mock file operations
        self.file_patch = patch('pathlib.Path.exists', return_value=True)
        self.file_patch.start()
        self.open_patch = patch('pathlib.Path.open', mock_open(read_data=self.creds_content))
        self.open_patch.start()
        self.touch_patch = patch('pathlib.Path.touch', return_value=None)
        self.touch_patch.start()
        self.write_patch = patch('pathlib.Path.write_text', return_value=None)
        self.write_patch.start()
        
        # Mock configparser write to prevent file writes
        self.config_write_patch = patch('configparser.ConfigParser.write')
        self.config_write_patch.start()
        
        reset_settings()
    
    def tearDown(self):
        """Clean up patches."""
        self.file_patch.stop()
        self.open_patch.stop()
        self.touch_patch.stop()
        self.write_patch.stop()
        self.config_write_patch.stop()
    
    def test_set_and_get_ssl_cert_path(self):
        """Test setting and retrieving SSL cert path."""
        settings = get_settings()
        ssl_path = "/etc/ssl/certs/custom.pem"
        
        settings.set_ssl_cert_path(ssl_path, "test-alias")
        retrieved_path = settings.get_ssl_cert_path("test-alias")
        
        self.assertEqual(retrieved_path, ssl_path)
    
    def test_get_ssl_cert_path_not_set(self):
        """Test retrieving SSL cert path when not configured."""
        settings = get_settings()
        retrieved_path = settings.get_ssl_cert_path("nonexistent-alias")
        
        self.assertIsNone(retrieved_path)
    
    def test_get_ssl_cert_path_empty_string(self):
        """Test retrieving SSL cert path when set to empty string."""
        settings = get_settings()
        settings.set_ssl_cert_path("", "test-alias")
        retrieved_path = settings.get_ssl_cert_path("test-alias")
        
        self.assertIsNone(retrieved_path)
    
    @patch.dict(os.environ, {"GEAI_SSL_CERT_PATH": "/env/cert.pem"})
    def test_env_variable_override_default_profile(self):
        """Test environment variable overrides default profile."""
        settings = get_settings()
        retrieved_path = settings.get_ssl_cert_path("default")
        
        self.assertEqual(retrieved_path, "/env/cert.pem")
    
    @patch.dict(os.environ, {"GEAI_SSL_CERT_PATH": "/env/cert.pem"})
    def test_env_variable_no_override_custom_profile(self):
        """Test environment variable does not override custom profiles."""
        settings = get_settings()
        settings.set_ssl_cert_path("/custom/cert.pem", "custom-alias")
        retrieved_path = settings.get_ssl_cert_path("custom-alias")
        
        self.assertEqual(retrieved_path, "/custom/cert.pem")
    
    def test_multiple_profiles_different_certs(self):
        """Test multiple profiles can have different SSL certs."""
        settings = get_settings()
        
        settings.set_ssl_cert_path("/corp/cert.pem", "corporate")
        settings.set_ssl_cert_path("/dev/cert.pem", "development")
        
        self.assertEqual(settings.get_ssl_cert_path("corporate"), "/corp/cert.pem")
        self.assertEqual(settings.get_ssl_cert_path("development"), "/dev/cert.pem")


class TestSessionLayer(TestCase):
    """Test SSL certificate handling in Session."""
    
    def setUp(self):
        """Mock credentials and reset session."""
        self.creds_content = """
[default]
geai_api_key = test_api_key
geai_api_base_url = https://api.test.com

[test-alias]
geai_api_key = test-key
geai_api_base_url = https://test.example.com
geai_ssl_cert_path = /test/cert.pem
"""
        self.file_patch = patch('pathlib.Path.exists', return_value=True)
        self.file_patch.start()
        self.open_patch = patch('pathlib.Path.open', mock_open(read_data=self.creds_content))
        self.open_patch.start()
        self.touch_patch = patch('pathlib.Path.touch', return_value=None)
        self.touch_patch.start()
        self.write_patch = patch('pathlib.Path.write_text', return_value=None)
        self.write_patch.start()
        self.config_write_patch = patch('configparser.ConfigParser.write')
        self.config_write_patch.start()
        
        reset_settings()
        reset_session()
    
    def tearDown(self):
        """Clean up patches."""
        self.file_patch.stop()
        self.open_patch.stop()
        self.touch_patch.stop()
        self.write_patch.stop()
        self.config_write_patch.stop()
    
    def test_session_loads_ssl_cert_from_config(self):
        """Test session loads SSL cert path from configuration."""
        settings = get_settings()
        settings.set_ssl_cert_path("/test/cert.pem", "test-alias")
        
        reset_session()
        session = get_session(alias="test-alias")
        
        self.assertEqual(session.ssl_cert_path, "/test/cert.pem")
    
    def test_session_ssl_cert_property_setter(self):
        """Test session SSL cert path can be updated via setter."""
        session = Session(
            api_key="test-key",
            base_url="https://test.example.com"
        )
        
        self.assertIsNone(session.ssl_cert_path)
        
        session.ssl_cert_path = "/updated/cert.pem"
        
        self.assertEqual(session.ssl_cert_path, "/updated/cert.pem")
    
    def test_session_default_ssl_cert_none(self):
        """Test session defaults to None for SSL cert path."""
        session = Session(
            api_key="test-key",
            base_url="https://test.example.com"
        )
        
        self.assertIsNone(session.ssl_cert_path)


class TestAPIServiceLayer(TestCase):
    """Test SSL certificate handling in API services."""
    
    def test_api_service_with_ssl_cert_path(self):
        """Test ApiService with custom SSL cert path."""
        api_service = ApiService(
            base_url="https://api.example.com",
            token="test-token",
            ssl_cert_path="/api/cert.pem"
        )
        
        self.assertEqual(api_service._get_ssl_verify(), "/api/cert.pem")
    
    def test_api_service_without_ssl_cert_path(self):
        """Test ApiService without SSL cert path defaults to True."""
        api_service = ApiService(
            base_url="https://api.example.com",
            token="test-token"
        )
        
        self.assertEqual(api_service._get_ssl_verify(), True)
    
    def test_geai_api_service_with_ssl_cert_path(self):
        """Test GEAIApiService with custom SSL cert path."""
        api_service = GEAIApiService(
            base_url="https://api.example.com",
            token="test-token",
            project_id="test-project",
            ssl_cert_path="/geai/cert.pem"
        )
        
        self.assertEqual(api_service._get_ssl_verify(), "/geai/cert.pem")
    
    def test_geai_api_service_without_ssl_cert_path(self):
        """Test GEAIApiService without SSL cert path defaults to True."""
        api_service = GEAIApiService(
            base_url="https://api.example.com",
            token="test-token",
            project_id="test-project"
        )
        
        self.assertEqual(api_service._get_ssl_verify(), True)


class TestClientLayer(TestCase):
    """Test SSL certificate handling in clients."""
    
    def setUp(self):
        """Mock credentials and reset."""
        self.creds_content = """
[default]
geai_api_key = test-key
geai_api_base_url = https://test.example.com

[test-alias]
geai_api_key = test-key
geai_api_base_url = https://test.example.com
geai_ssl_cert_path = /client/cert.pem
"""
        self.file_patch = patch('pathlib.Path.exists', return_value=True)
        self.file_patch.start()
        self.open_patch = patch('pathlib.Path.open', mock_open(read_data=self.creds_content))
        self.open_patch.start()
        self.touch_patch = patch('pathlib.Path.touch', return_value=None)
        self.touch_patch.start()
        self.write_patch = patch('pathlib.Path.write_text', return_value=None)
        self.write_patch.start()
        self.config_write_patch = patch('configparser.ConfigParser.write')
        self.config_write_patch.start()
        
        reset_settings()
        reset_session()
    
    def tearDown(self):
        """Clean up patches."""
        self.file_patch.stop()
        self.open_patch.stop()
        self.touch_patch.stop()
        self.write_patch.stop()
        self.config_write_patch.stop()
    
    def test_client_loads_ssl_cert_from_alias(self):
        """Test client loads SSL cert from configured alias."""
        settings = get_settings()
        settings.set_ssl_cert_path("/client/cert.pem", "test-alias")
        
        reset_session()
        client = BaseClient(alias="test-alias")
        
        self.assertEqual(client.session.ssl_cert_path, "/client/cert.pem")
        self.assertEqual(client.api_service._get_ssl_verify(), "/client/cert.pem")
    
    def test_client_without_ssl_cert(self):
        """Test client without SSL cert uses default verification."""
        reset_session()
        client = BaseClient(alias="default")
        
        self.assertIsNone(client.session.ssl_cert_path)
        self.assertEqual(client.api_service._get_ssl_verify(), True)


class TestIntegrationFlow(TestCase):
    """Integration tests for full SSL certificate flow."""
    
    def setUp(self):
        """Mock credentials and reset."""
        self.creds_content = """
[default]
geai_api_key = test-key
geai_api_base_url = https://test.example.com

[integration]
geai_api_key = integration-key
geai_api_base_url = https://integration.example.com
geai_ssl_cert_path = /integration/cert.pem
"""
        self.file_patch = patch('pathlib.Path.exists', return_value=True)
        self.file_patch.start()
        self.open_patch = patch('pathlib.Path.open', mock_open(read_data=self.creds_content))
        self.open_patch.start()
        self.touch_patch = patch('pathlib.Path.touch', return_value=None)
        self.touch_patch.start()
        self.write_patch = patch('pathlib.Path.write_text', return_value=None)
        self.write_patch.start()
        self.config_write_patch = patch('configparser.ConfigParser.write')
        self.config_write_patch.start()
        
        reset_settings()
        reset_session()
    
    def tearDown(self):
        """Clean up patches."""
        self.file_patch.stop()
        self.open_patch.stop()
        self.touch_patch.stop()
        self.write_patch.stop()
        self.config_write_patch.stop()
    
    def test_full_flow_config_to_api_service(self):
        """Test complete flow from config to API service."""
        settings = get_settings()
        settings.set_ssl_cert_path("/integration/cert.pem", "integration")
        
        reset_session()
        session = get_session(alias="integration")
        self.assertEqual(session.ssl_cert_path, "/integration/cert.pem")
        
        client = BaseClient(alias="integration")
        self.assertEqual(client.session.ssl_cert_path, "/integration/cert.pem")
        self.assertEqual(client.api_service._get_ssl_verify(), "/integration/cert.pem")
    
    def test_backward_compatibility_no_ssl_cert(self):
        """Test backward compatibility with no SSL cert configured."""
        reset_session()
        
        session = get_session(alias="default")
        self.assertIsNone(session.ssl_cert_path)
        
        client = BaseClient(alias="default")
        self.assertIsNone(client.session.ssl_cert_path)
        self.assertEqual(client.api_service._get_ssl_verify(), True)
    
    @patch.dict(os.environ, {"GEAI_SSL_CERT_PATH": "/env/integration/cert.pem"})
    def test_env_variable_integration(self):
        """Test environment variable integration in full flow."""
        reset_settings()
        reset_session()
        
        session = get_session(alias="default")
        self.assertEqual(session.ssl_cert_path, "/env/integration/cert.pem")
        
        client = BaseClient(alias="default")
        self.assertEqual(client.api_service._get_ssl_verify(), "/env/integration/cert.pem")


class TestEdgeCases(TestCase):
    """Test edge cases and error conditions."""
    
    def setUp(self):
        """Mock credentials."""
        self.creds_content = """
[default]
geai_api_key = test-key
geai_api_base_url = https://test.example.com
"""
        self.file_patch = patch('pathlib.Path.exists', return_value=True)
        self.file_patch.start()
        self.open_patch = patch('pathlib.Path.open', mock_open(read_data=self.creds_content))
        self.open_patch.start()
        self.touch_patch = patch('pathlib.Path.touch', return_value=None)
        self.touch_patch.start()
        self.write_patch = patch('pathlib.Path.write_text', return_value=None)
        self.write_patch.start()
        self.config_write_patch = patch('configparser.ConfigParser.write')
        self.config_write_patch.start()
        
        reset_settings()
        reset_session()
    
    def tearDown(self):
        """Clean up patches."""
        self.file_patch.stop()
        self.open_patch.stop()
        self.touch_patch.stop()
        self.write_patch.stop()
        self.config_write_patch.stop()
    
    def test_ssl_cert_path_with_spaces(self):
        """Test SSL cert path with spaces in filename."""
        settings = get_settings()
        ssl_path = "/path/with spaces/cert.pem"
        
        settings.set_ssl_cert_path(ssl_path, "spaces-test")
        retrieved_path = settings.get_ssl_cert_path("spaces-test")
        
        self.assertEqual(retrieved_path, ssl_path)
    
    def test_ssl_cert_path_unicode_characters(self):
        """Test SSL cert path with unicode characters."""
        settings = get_settings()
        ssl_path = "/path/cërtificåtë/cert.pem"
        
        settings.set_ssl_cert_path(ssl_path, "unicode-test")
        retrieved_path = settings.get_ssl_cert_path("unicode-test")
        
        self.assertEqual(retrieved_path, ssl_path)
    
    def test_ssl_cert_path_relative_path(self):
        """Test SSL cert path with relative path."""
        settings = get_settings()
        ssl_path = "./certs/custom.pem"
        
        settings.set_ssl_cert_path(ssl_path, "relative-test")
        retrieved_path = settings.get_ssl_cert_path("relative-test")
        
        self.assertEqual(retrieved_path, ssl_path)
    
    def test_updating_ssl_cert_path(self):
        """Test updating SSL cert path for existing profile."""
        settings = get_settings()
        
        settings.set_ssl_cert_path("/old/cert.pem", "update-test")
        self.assertEqual(settings.get_ssl_cert_path("update-test"), "/old/cert.pem")
        
        settings.set_ssl_cert_path("/new/cert.pem", "update-test")
        self.assertEqual(settings.get_ssl_cert_path("update-test"), "/new/cert.pem")
    
    def test_removing_ssl_cert_path(self):
        """Test removing SSL cert path by setting to empty string."""
        settings = get_settings()
        
        settings.set_ssl_cert_path("/cert.pem", "remove-test")
        self.assertEqual(settings.get_ssl_cert_path("remove-test"), "/cert.pem")
        
        settings.set_ssl_cert_path("", "remove-test")
        self.assertIsNone(settings.get_ssl_cert_path("remove-test"))


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