"""Tests for the explicit remote-model egress boundary."""

from __future__ import annotations

import hashlib

import pytest

from cyberbrain26.data_plane import SQLiteDatabase
from cyberbrain26.security_plane import (
    AuditEventType,
    AuditLog,
    AuditQuery,
    DataClassification,
    EgressGate,
    EgressPolicy,
    EgressRequest,
    SecurityError,
)
from cyberbrain26.security_plane.extension_points import EgressPermissionGate


def request(
    *,
    classifications: frozenset[DataClassification] | None = None,
    provider_id: str = "approved-local-proxy",
    destination: str = "",
) -> EgressRequest:
    return EgressRequest(
        agent_id="knowledge-agent",
        provider_id=provider_id,
        payload_sha256=hashlib.sha256(b"synthetic payload").hexdigest(),
        classifications=classifications or frozenset({DataClassification.PUBLIC}),
        destination=destination,
    )


def test_remote_egress_is_denied_without_an_explicit_provider_allowlist() -> None:
    gate = EgressGate(audit_log=AuditLog(), base_decision=True)

    with pytest.raises(SecurityError, match="allowlist"):
        gate.authorize(request())


def test_remote_egress_requires_audit_and_refuses_personal_data_by_default() -> None:
    policy = EgressPolicy(allowed_remote_providers=frozenset({"approved-local-proxy"}))
    gate_with_audit = EgressGate(policy, audit_log=AuditLog(), base_decision=True)

    # A gate that cannot record a decision must not be constructible at all,
    # so an unaudited egress can never be attempted in the first place.
    with pytest.raises(TypeError):
        EgressGate(policy)  # type: ignore[call-arg]

    with pytest.raises(SecurityError, match="classifications"):
        gate_with_audit.authorize(request(classifications=frozenset({DataClassification.PERSONAL})))


def test_allowed_remote_egress_records_only_a_digest_and_classification(tmp_path) -> None:
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    policy = EgressPolicy(
        allowed_remote_providers=frozenset({"approved-local-proxy"}),
        allowed_remote_classifications=frozenset({DataClassification.PUBLIC}),
    )

    EgressGate(policy, audit_log=audit, base_decision=True).authorize(request())

    event = audit.query(AuditQuery(event_type=AuditEventType.DATA_ACCESS))[0]
    assert event.action == "model_egress"
    assert event.result == "allowed"
    assert event.metadata["classifications"] == ["public"]
    assert "synthetic payload" not in str(event.metadata)


def test_provider_in_neither_allowlist_is_denied_by_default() -> None:
    """Negative test: no default-open path exists through this boundary."""
    gate = EgressGate(audit_log=AuditLog(), base_decision=True)

    with pytest.raises(SecurityError, match="allowlist"):
        gate.authorize(request())


def test_local_allowlist_does_not_grant_remote_egress(tmp_path) -> None:
    """A provider listed as local must not slip past the classification gate."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    policy = EgressPolicy(
        allowed_remote_providers=frozenset(),
        local_provider_ids=frozenset({"approved-local-proxy"}),
    )
    gate = EgressGate(policy, audit_log=audit, base_decision=True)

    # Local egress is permitted, and is recorded as local - never as remote.
    gate.authorize(request())

    event = audit.query(AuditQuery(event_type=AuditEventType.DATA_ACCESS))[0]
    assert event.network_action == "local"

    # A provider listed nowhere at all is still denied.
    with pytest.raises(SecurityError, match="allowlist"):
        gate.authorize(request(provider_id="not-listed-anywhere"))


def test_local_provider_still_passes_a_classification_gate() -> None:
    """Negative test: a local provider is not a licence to skip the gate.

    Naming a provider "local" only proves the owner listed it.  If that were
    enough to skip classification, the allowlist would be an exfiltration
    bypass under another name.
    """
    policy = EgressPolicy(
        allowed_remote_providers=frozenset(),
        local_provider_ids=frozenset({"approved-local-proxy"}),
    )
    gate = EgressGate(policy, audit_log=AuditLog(), base_decision=True)

    # RESTRICTED is excluded from local egress by default ...
    with pytest.raises(SecurityError, match="classifications"):
        gate.authorize(
            request(
                classifications=frozenset({DataClassification.RESTRICTED}),
                destination="http://localhost:11434/v1",
            )
        )


def test_local_provider_cannot_claim_a_non_loopback_destination() -> None:
    """Negative test: "local" is proven by the destination, not by the name."""
    policy = EgressPolicy(
        allowed_remote_providers=frozenset(),
        local_provider_ids=frozenset({"approved-local-proxy"}),
    )
    gate = EgressGate(policy, audit_log=AuditLog(), base_decision=True)

    with pytest.raises(SecurityError, match="loopback"):
        gate.authorize(request(destination="https://models.example.com/v1"))


def test_local_egress_to_loopback_is_allowed_and_recorded(tmp_path) -> None:
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    policy = EgressPolicy(local_provider_ids=frozenset({"approved-local-proxy"}))
    gate = EgressGate(policy, audit_log=audit, base_decision=True)

    gate.authorize(
        request(
            classifications=frozenset({DataClassification.PERSONAL}),
            destination="http://127.0.0.1:11434/v1",
        )
    )

    event = audit.query(AuditQuery(event_type=AuditEventType.DATA_ACCESS))[0]
    assert event.network_action == "local"
    assert event.result == "allowed"


def test_destination_sanitiser_strips_credentials_and_query_secrets(tmp_path) -> None:
    """Negative test: secrets must not reach the audit row."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    policy = EgressPolicy(allowed_remote_providers=frozenset({"approved-local-proxy"}))

    EgressGate(policy, audit_log=audit, base_decision=True).authorize(
        request(destination="https://user:supersecret@provider.example:443/v1/chat?api_key=TOPSECRET")
    )

    metadata = str(audit.query(AuditQuery(event_type=AuditEventType.DATA_ACCESS))[0].metadata)
    assert "supersecret" not in metadata
    assert "TOPSECRET" not in metadata
    assert "user:" not in metadata
    assert "provider.example:443" in metadata


def test_destination_sanitiser_survives_percent_encoded_separators(tmp_path) -> None:
    """Negative test: percent-encoded userinfo must not survive either."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    policy = EgressPolicy(allowed_remote_providers=frozenset({"approved-local-proxy"}))

    EgressGate(policy, audit_log=audit, base_decision=True).authorize(
        request(destination="https://user%40evil:pass@provider.example/v1")
    )

    metadata = str(audit.query(AuditQuery(event_type=AuditEventType.DATA_ACCESS))[0].metadata)
    assert "pass" not in metadata
    assert "%40" not in metadata
    assert "evil" not in metadata
    assert "provider.example" in metadata


def test_remote_egress_denied_when_classifications_empty(tmp_path) -> None:
    """Remote egress s prázdnou klasifikací (fail-closed) selže."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    policy = EgressPolicy(
        allowed_remote_providers=frozenset({"remote"}),
        allowed_remote_classifications=frozenset(),  # prázdná → odmítnout
    )
    with pytest.raises(SecurityError, match="remote classifications denied"):
        EgressGate(policy, audit_log=audit, base_decision=True).authorize(
            request(provider_id="remote", classifications=frozenset(), destination="http://example.com")
        )


def test_local_egress_allowed_with_public_classification(tmp_path) -> None:
    """Local egress s PUBLIC klasifikací je po implicitním whitelistu povolený."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    policy = EgressPolicy(local_provider_ids=frozenset({"local"}))
    # implicitní allowed_local_classifications = {PUBLIC, PERSONAL, SENSITIVE}
    EgressGate(policy, audit_log=audit, base_decision=True).authorize(
        request(
            provider_id="local",
            classifications=frozenset({DataClassification.PUBLIC}),
            destination="http://localhost",
        )
    )

    # Ověření se provádí implicitně přes audit; žádný SecurityError nebylo vyvoláno.


def test_egress_permission_gate_deny_all_when_disabled(tmp_path) -> None:
    """EgressPermissionGate s base_decision=False odmítne všechny egress."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    policy = EgressPolicy(
        allowed_remote_providers=frozenset({"remote"}),
        allowed_remote_classifications=frozenset({DataClassification.PUBLIC}),
    )
    gate = EgressGate(policy, audit_log=audit)
    # Přeepíšeme gate, aby explicitně odmítl všechno
    gate.egress_permission_gate = EgressPermissionGate(base_decision=False)
    with pytest.raises(SecurityError, match="egress permission denied"):
        gate.authorize(
            request(
                provider_id="remote",
                classifications=frozenset({DataClassification.PUBLIC}),
                destination="http://example.com",
            )
        )
