"""Canary detection tests — verify the LLM reviewer catches known-bad content.

These tests are the "bombword" red-team layer: they create synthetic files
containing terms that must NEVER appear in the public mirror, then assert
the reviewer flags them. If any canary test passes clean, the gate is broken.

This file itself contains the canary terms only inside strings passed to the
LLM — it does not constitute a leak, since the test file is never the subject
of review and the terms appear in a clearly controlled test context.

Design: each canary covers a different category of sensitive content so that
regressions in one category don't hide behind passes in others.
"""

from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock

import pytest

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_gateway_with_response(response_text: str):
    """Build a mock gateway that returns a fixed LLM response."""
    gateway = MagicMock()
    mock_resp = MagicMock()
    mock_resp.text = response_text
    gateway.complete.return_value = mock_resp
    return gateway


def _real_gateway():
    """Return the real gateway if a reachable LLM is configured, else None.

    Excludes VPN-only providers that are not currently reachable, so tests
    skip cleanly when running off-network rather than failing with misleading
    'CLEAR' verdicts caused by provider fallback to an unconfigured stub.
    """
    try:
        from axiom.infra.gateway import Gateway
        g = Gateway()
        # Only count providers that are either not VPN-gated, or VPN is up
        reachable = [
            p for p in g.providers
            if p.api_key and (not p.requires_vpn or g._check_vpn(p))
        ]
        return g if reachable else None
    except Exception:
        return None


# ---------------------------------------------------------------------------
# Unit canaries — mock gateway, deterministic
# ---------------------------------------------------------------------------

class TestUnitCanaries:
    """Fast canaries using mocked LLM responses.

    These verify that the reviewer correctly interprets REVIEW_NEEDED responses
    for each sensitive content category. They don't hit the real LLM.
    """

    CATEGORIES = [
        (
            "staff_name",
            "Dr. Kevin Clarno is the department head.",
            "VERDICT: REVIEW_NEEDED\nFINDINGS:\n- Staff name: Kevin Clarno\nRECOMMENDATION: Redact.",
        ),
        (
            "internal_hostname",
            "Connect to gitlab.internal.example.com for access.",
            "VERDICT: REVIEW_NEEDED\nFINDINGS:\n- Internal hostname: gitlab.internal.example.com\nRECOMMENDATION: Redact.",
        ),
        (
            "ip_address",
            "Server is at 128.62.248.232, Ubuntu 24.04.",
            "VERDICT: REVIEW_NEEDED\nFINDINGS:\n- Internal IP address: 128.62.248.232\nRECOMMENDATION: Remove.",
        ),
        (
            "budget_figure",
            "AWS budget request: $1,300/month for medium tier.",
            "VERDICT: REVIEW_NEEDED\nFINDINGS:\n- Budget figure: $1,300/month\nRECOMMENDATION: Remove financial details.",
        ),
        (
            "project_codename",
            "The Sensor_Grid project is tracked in Linear.",
            "VERDICT: REVIEW_NEEDED\nFINDINGS:\n- Internal project codename: Sensor_Grid\nRECOMMENDATION: Redact.",
        ),
        (
            "student_name",
            "John Smith leads the Project Alpha.",
            "VERDICT: REVIEW_NEEDED\nFINDINGS:\n- Staff/student name: John Smith\nRECOMMENDATION: Redact.",
        ),
        (
            "grant_details",
            "NSF 2026 grant portfolio — multiple PIs, pre-decisional.",
            "VERDICT: REVIEW_NEEDED\nFINDINGS:\n- Pre-decisional grant reference: NSF 2026\nRECOMMENDATION: Remove.",
        ),
    ]

    @pytest.mark.parametrize("category,content,llm_response", CATEGORIES)
    def test_canary_flagged(self, category, content, llm_response, tmp_path):
        """Canary content in category '{category}' must be flagged REVIEW_NEEDED."""
        from axiom.extensions.builtins.mirror_agent.reviewer import _review_file

        canary = tmp_path / f"canary_{category}.py"
        canary.write_text(f"# Canary test\nSECRET = '{content}'\n")

        gateway = _make_gateway_with_response(llm_response)
        review = _review_file(canary, tmp_path, gateway)

        assert review.verdict == "REVIEW_NEEDED", (
            f"Canary '{category}' was not flagged — gate may be broken.\n"
            f"Content: {content!r}\nFindings: {review.findings}"
        )
        assert len(review.findings) >= 1, (
            f"Canary '{category}' flagged but no findings extracted."
        )


# ---------------------------------------------------------------------------
# Integration canaries — real LLM, skipped if no provider
# ---------------------------------------------------------------------------

@pytest.mark.integration
class TestIntegrationCanaries:
    """Live canaries that send synthetic sensitive content to the real LLM.

    These run in CI where ANTHROPIC_API_KEY is set, and can be run locally
    with a configured LLM. They validate that the actual model catches leaks,
    not just that we parse structured responses correctly.
    """

    @pytest.fixture(autouse=True)
    def require_llm(self):
        gateway = _real_gateway()
        if gateway is None:
            pytest.skip("No LLM provider configured")
        self.gateway = gateway

    def _assert_flagged(self, content: str, category: str, tmp_path: Path):
        from axiom.extensions.builtins.mirror_agent.reviewer import _review_file
        canary = tmp_path / f"canary_{category}.txt"
        canary.write_text(content)
        review = _review_file(canary, tmp_path, self.gateway)
        assert review.verdict == "REVIEW_NEEDED", (
            f"LLM missed canary '{category}': {content!r}\n"
            f"Findings: {review.findings}\nRecommendation: {review.recommendation}"
        )

    def test_catches_internal_hostname(self, tmp_path):
        self._assert_flagged(
            "Primary server: gitlab.internal.example.com — requires VPN.",
            "hostname", tmp_path,
        )

    def test_catches_ip_address(self, tmp_path):
        self._assert_flagged(
            "Host: 128.62.248.232 (Ubuntu 24.04, neut serve running)",
            "ip", tmp_path,
        )

    def test_catches_budget_figure(self, tmp_path):
        self._assert_flagged(
            "Infrastructure cost estimate: $1,300/month (recommended medium tier).",
            "budget", tmp_path,
        )

    def test_catches_full_staff_name(self, tmp_path):
        self._assert_flagged(
            "Contact Dr. Kevin Clarno for budget approval.",
            "staff_name", tmp_path,
        )

    def test_catches_student_name(self, tmp_path):
        self._assert_flagged(
            "Jane Doe built the live monitoring dashboard.",
            "student_name", tmp_path,
        )

    def test_catches_internal_project_codename(self, tmp_path):
        self._assert_flagged(
            "The Sensor_Grid project integrates sensor data from the DAQ system.",
            "codename", tmp_path,
        )

    def test_does_not_flag_generic_technical_terms(self, tmp_path):
        """Technical terminology should not be flagged as sensitive."""
        from axiom.extensions.builtins.mirror_agent.reviewer import _review_file
        canary = tmp_path / "simulation.py"
        canary.write_text(
            "# Monte Carlo simulation\n"
            "# Uses standard deviation for uncertainty quantification\n"
            "CONVERGENCE_THRESHOLD = 0.001\n"
            "TIME_STEP_UNIT = 'seconds'\n"
        )
        review = _review_file(canary, tmp_path, self.gateway)
        assert review.verdict == "CLEAR", (
            f"Generic technical terms incorrectly flagged.\n"
            f"Findings: {review.findings}"
        )
