"""Tests for mTLS support (F8)."""

from __future__ import annotations

import os
import ssl
import tempfile
from unittest.mock import patch

import pytest

from qremotex.connection.mtls import MTLSConfig, build_ssl_context


# ──────────────────────────────────────────────── MTLSConfig ──


class TestMTLSConfig:
    def test_disabled_by_default(self) -> None:
        with patch.dict(os.environ, {}, clear=False):
            os.environ.pop("QRAPTOR_MTLS_ENABLED", None)
            config = MTLSConfig()
            assert config.enabled is False

    def test_enabled(self) -> None:
        with patch.dict(os.environ, {"QRAPTOR_MTLS_ENABLED": "true"}):
            config = MTLSConfig()
            assert config.enabled is True

    def test_env_loading(self) -> None:
        with patch.dict(os.environ, {
            "QRAPTOR_MTLS_ENABLED": "true",
            "QRAPTOR_MTLS_CERT_FILE": "/path/to/cert.pem",
            "QRAPTOR_MTLS_KEY_FILE": "/path/to/key.pem",
            "QRAPTOR_MTLS_CA_FILE": "/path/to/ca.pem",
        }):
            config = MTLSConfig()
            assert config.cert_file == "/path/to/cert.pem"
            assert config.key_file == "/path/to/key.pem"
            assert config.ca_file == "/path/to/ca.pem"


# ──────────────────────────────────────────────── Validation ──


class TestMTLSValidation:
    def test_disabled_no_errors(self) -> None:
        with patch.dict(os.environ, {}, clear=False):
            os.environ.pop("QRAPTOR_MTLS_ENABLED", None)
            config = MTLSConfig()
            assert config.validate() == []

    def test_missing_cert_file(self) -> None:
        with patch.dict(os.environ, {
            "QRAPTOR_MTLS_ENABLED": "true",
            "QRAPTOR_MTLS_KEY_FILE": "/some/key.pem",
        }):
            os.environ.pop("QRAPTOR_MTLS_CERT_FILE", None)
            config = MTLSConfig()
            errors = config.validate()
            assert any("CERT_FILE" in e for e in errors)

    def test_missing_key_file(self) -> None:
        with patch.dict(os.environ, {
            "QRAPTOR_MTLS_ENABLED": "true",
            "QRAPTOR_MTLS_CERT_FILE": "/some/cert.pem",
        }):
            os.environ.pop("QRAPTOR_MTLS_KEY_FILE", None)
            config = MTLSConfig()
            errors = config.validate()
            assert any("KEY_FILE" in e for e in errors)

    def test_cert_file_not_found(self) -> None:
        with patch.dict(os.environ, {
            "QRAPTOR_MTLS_ENABLED": "true",
            "QRAPTOR_MTLS_CERT_FILE": "/nonexistent/cert.pem",
            "QRAPTOR_MTLS_KEY_FILE": "/nonexistent/key.pem",
        }):
            config = MTLSConfig()
            errors = config.validate()
            assert any("not found" in e for e in errors)

    def test_ca_file_not_found(self) -> None:
        # Create real cert/key files but non-existent CA
        with tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as cert, \
             tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as key:
            cert.write(b"dummy-cert")
            key.write(b"dummy-key")
            cert_path = cert.name
            key_path = key.name

        try:
            with patch.dict(os.environ, {
                "QRAPTOR_MTLS_ENABLED": "true",
                "QRAPTOR_MTLS_CERT_FILE": cert_path,
                "QRAPTOR_MTLS_KEY_FILE": key_path,
                "QRAPTOR_MTLS_CA_FILE": "/nonexistent/ca.pem",
            }):
                config = MTLSConfig()
                errors = config.validate()
                assert any("CA_FILE" in e for e in errors)
        finally:
            os.unlink(cert_path)
            os.unlink(key_path)

    def test_valid_config_files_exist(self) -> None:
        with tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as cert, \
             tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as key:
            cert.write(b"dummy-cert")
            key.write(b"dummy-key")
            cert_path = cert.name
            key_path = key.name

        try:
            with patch.dict(os.environ, {
                "QRAPTOR_MTLS_ENABLED": "true",
                "QRAPTOR_MTLS_CERT_FILE": cert_path,
                "QRAPTOR_MTLS_KEY_FILE": key_path,
            }):
                os.environ.pop("QRAPTOR_MTLS_CA_FILE", None)
                config = MTLSConfig()
                errors = config.validate()
                assert errors == []
        finally:
            os.unlink(cert_path)
            os.unlink(key_path)


# ──────────────────────────────────────────────── build_ssl_context ──


class TestBuildSSLContext:
    def test_disabled_returns_none(self) -> None:
        with patch.dict(os.environ, {}, clear=False):
            os.environ.pop("QRAPTOR_MTLS_ENABLED", None)
            config = MTLSConfig()
            ctx = build_ssl_context(config)
            assert ctx is None

    def test_invalid_config_raises(self) -> None:
        with patch.dict(os.environ, {"QRAPTOR_MTLS_ENABLED": "true"}):
            os.environ.pop("QRAPTOR_MTLS_CERT_FILE", None)
            os.environ.pop("QRAPTOR_MTLS_KEY_FILE", None)
            config = MTLSConfig()
            with pytest.raises(ValueError, match="mTLS configuration errors"):
                build_ssl_context(config)

    def test_valid_config_returns_context(self) -> None:
        """Test that a valid config produces an SSLContext.

        Note: We can't use dummy cert/key content — ssl.SSLContext.load_cert_chain
        requires real PEM data. Since generating self-signed certs in tests is fragile,
        we mock the ssl.SSLContext methods.
        """
        with tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as cert, \
             tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as key:
            cert.write(b"dummy-cert")
            key.write(b"dummy-key")
            cert_path = cert.name
            key_path = key.name

        try:
            with patch.dict(os.environ, {
                "QRAPTOR_MTLS_ENABLED": "true",
                "QRAPTOR_MTLS_CERT_FILE": cert_path,
                "QRAPTOR_MTLS_KEY_FILE": key_path,
            }):
                os.environ.pop("QRAPTOR_MTLS_CA_FILE", None)
                config = MTLSConfig()
                # Mock load_cert_chain since dummy files aren't real PEM
                with patch.object(ssl.SSLContext, "load_cert_chain"), \
                     patch.object(ssl.SSLContext, "load_default_certs"):
                    ctx = build_ssl_context(config)
                    assert isinstance(ctx, ssl.SSLContext)
                    assert ctx.verify_mode == ssl.CERT_REQUIRED
                    assert ctx.check_hostname is True
                    assert ctx.minimum_version == ssl.TLSVersion.TLSv1_2
        finally:
            os.unlink(cert_path)
            os.unlink(key_path)

    def test_with_ca_file(self) -> None:
        with tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as cert, \
             tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as key, \
             tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as ca:
            cert.write(b"dummy-cert")
            key.write(b"dummy-key")
            ca.write(b"dummy-ca")
            cert_path, key_path, ca_path = cert.name, key.name, ca.name

        try:
            with patch.dict(os.environ, {
                "QRAPTOR_MTLS_ENABLED": "true",
                "QRAPTOR_MTLS_CERT_FILE": cert_path,
                "QRAPTOR_MTLS_KEY_FILE": key_path,
                "QRAPTOR_MTLS_CA_FILE": ca_path,
            }):
                config = MTLSConfig()
                with patch.object(ssl.SSLContext, "load_cert_chain"), \
                     patch.object(ssl.SSLContext, "load_verify_locations"):
                    ctx = build_ssl_context(config)
                    assert isinstance(ctx, ssl.SSLContext)
        finally:
            os.unlink(cert_path)
            os.unlink(key_path)
            os.unlink(ca_path)
