"""Config model invariants.

These pin the two rules that are cheap to get wrong and expensive to debug
against a live Control Room: the mutually exclusive secret, and writes never
defaulting to enabled.
"""

import pytest
from pydantic import ValidationError

from a360_mcp.utils.config import A360AuthConfig, AuthType, ServerConfig


def make_auth(**overrides) -> A360AuthConfig:
    payload = {"username": "svc_mcp", "api_key": "test-key"}
    payload.update(overrides)
    return A360AuthConfig(**payload)


class TestA360AuthConfig:
    def test_api_key_auth_reports_api_key_type(self):
        auth = make_auth()
        assert auth.auth_type is AuthType.API_KEY

    def test_password_auth_reports_password_type(self):
        auth = make_auth(api_key=None, password="secret")
        assert auth.auth_type is AuthType.PASSWORD

    def test_rejects_api_key_and_password_together(self):
        # The v2 spec declares these under anyOf; sending both is a 400.
        with pytest.raises(ValidationError, match="cannot be used together"):
            make_auth(password="secret")

    def test_rejects_missing_secret(self):
        with pytest.raises(ValidationError, match="one of api_key or password"):
            make_auth(api_key=None)

    def test_rejects_blank_username(self):
        with pytest.raises(ValidationError, match="username must not be blank"):
            make_auth(username="   ")

    def test_auth_request_body_uses_api_key_field(self):
        body = make_auth().to_auth_request()
        assert body == {"username": "svc_mcp", "multipleLogin": False, "apiKey": "test-key"}

    def test_auth_request_body_omits_api_key_when_password_used(self):
        body = make_auth(api_key=None, password="secret").to_auth_request()
        assert "apiKey" not in body
        assert body["password"] == "secret"


class TestSecretsNeverSurface:
    """The only place a secret may appear in plaintext is `to_auth_request()`.

    Everything else — repr, str, validation errors — is a logging surface:
    `cli.main` logs a failed `create_config` verbatim to stderr and to the
    rotating log file, so a secret echoed by pydantic lands on disk.
    """

    SECRET = "SUPERSECRET123"

    def test_validation_error_does_not_echo_the_secret(self):
        with pytest.raises(ValidationError) as exc_info:
            A360AuthConfig(username="svc_mcp", api_key=self.SECRET, password="PW_SECRET")
        rendered = str(exc_info.value)
        assert self.SECRET not in rendered
        assert "PW_SECRET" not in rendered
        # The actionable part of the message must survive the redaction.
        assert "cannot be used together" in rendered

    def test_field_validation_error_does_not_echo_the_secret(self):
        with pytest.raises(ValidationError) as exc_info:
            A360AuthConfig(username="   ", api_key=self.SECRET)
        assert self.SECRET not in str(exc_info.value)

    def test_repr_masks_the_api_key(self):
        rendered = repr(make_auth(api_key=self.SECRET))
        assert self.SECRET not in rendered
        assert "svc_mcp" in rendered

    def test_repr_masks_the_password(self):
        rendered = repr(make_auth(api_key=None, password=self.SECRET))
        assert self.SECRET not in rendered

    def test_server_config_repr_masks_nested_secret(self):
        cfg = ServerConfig(
            control_room_url="https://cr.example.com",
            auth=make_auth(api_key=self.SECRET),
        )
        assert self.SECRET not in repr(cfg)

    def test_server_config_validation_error_does_not_echo_the_secret(self):
        with pytest.raises(ValidationError) as exc_info:
            ServerConfig(
                control_room_url="cr.example.com",  # missing scheme
                auth={"username": "svc_mcp", "api_key": self.SECRET},
            )
        assert self.SECRET not in str(exc_info.value)

    def test_auth_request_still_carries_the_plaintext_secret(self):
        # The unwrap point stays deliberate: the Control Room needs the real value.
        assert make_auth(api_key=self.SECRET).to_auth_request()["apiKey"] == self.SECRET


class TestServerConfig:
    def make(self, **overrides) -> ServerConfig:
        payload = {"control_room_url": "https://cr.example.com", "auth": make_auth()}
        payload.update(overrides)
        return ServerConfig(**payload)

    def test_writes_are_disabled_by_default(self):
        # A missing flag must never mean "writable".
        assert self.make().allow_writes is False

    def test_trailing_slash_is_stripped(self):
        assert self.make(control_room_url="https://cr.example.com/").api_url == (
            "https://cr.example.com"
        )

    def test_rejects_url_without_scheme(self):
        with pytest.raises(ValidationError, match="must start with http"):
            self.make(control_room_url="cr.example.com")

    def test_auth_url_targets_v2_not_v1(self):
        # /v1/authentication does not exist in A360; only /token, /logout, /app/login do.
        assert self.make().auth_url == "https://cr.example.com/v2/authentication"

    def test_token_refresh_url(self):
        assert self.make().token_refresh_url == "https://cr.example.com/v2/authentication/token"

    def test_verify_defaults_to_true(self):
        assert self.make().verify is True

    def test_verify_uses_ca_bundle_when_given(self):
        assert self.make(ca_bundle="/etc/ssl/corp.pem").verify == "/etc/ssl/corp.pem"

    def test_verify_false_overrides_ca_bundle(self):
        cfg = self.make(ca_bundle="/etc/ssl/corp.pem", tls_verify=False)
        assert cfg.verify is False
