"""Tests for AI-enhanced explanation generation."""

from __future__ import annotations

import json
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch

if TYPE_CHECKING:
    from pathlib import Path

import httpx
import pytest

from complyform.core.assessor.control_matcher import Finding
from complyform.core.errors import AIExplainAPIError, AIExplainKeyMissingError
from complyform.core.explainer.ai_enhanced import (
    _AI_MODEL,
    EXPLAIN_CACHE_TTL_SECONDS,
    _compute_cache_key,
    _parse_ai_response,
    ai_explain_finding,
    resolve_api_key,
)


def _make_finding(**overrides: object) -> Finding:
    defaults: dict[str, object] = {
        "control_id": "1.1",
        "framework": "cis_gcp",
        "resource_address": "google_compute_instance.web",
        "resource_type": "google_compute_instance",
        "status": "FAIL",
        "severity": "HIGH",
        "finding": "Instance does not use shielded VM",
        "remediation": "Enable shielded VM in instance config",
        "checkov_id": "CKV_GCP_39",
        "cross_mappings": None,
        "native_ids": None,
        "confidence": "high",
    }
    defaults.update(overrides)
    return Finding.model_validate(defaults)


class TestResolveAPIKey:
    def test_config_key_preferred(self) -> None:
        key = resolve_api_key("sk-config-key")
        assert key == "sk-config-key"

    def test_env_var_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-env-key")
        key = resolve_api_key(None)
        assert key == "sk-env-key"

    def test_missing_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
        with pytest.raises(AIExplainKeyMissingError):
            resolve_api_key(None)


class TestCacheKey:
    def test_deterministic(self) -> None:
        finding = _make_finding()
        key1 = _compute_cache_key(finding)
        key2 = _compute_cache_key(finding)
        assert key1 == key2
        assert len(key1) == 24

    def test_different_findings_different_keys(self) -> None:
        f1 = _make_finding(control_id="1.1")
        f2 = _make_finding(control_id="1.2")
        assert _compute_cache_key(f1) != _compute_cache_key(f2)


class TestParseAIResponse:
    def test_parses_structured_response(self) -> None:
        text = (
            "SUMMARY: This is a summary.\n"
            "WHY_IT_MATTERS: This matters because of security.\n"
            "REMEDIATION_HINT: Add the config option."
        )
        sections = _parse_ai_response(text)
        assert "summary" in sections
        assert "why_it_matters" in sections
        assert "remediation_hint" in sections

    def test_multiline_sections(self) -> None:
        text = (
            "SUMMARY: Line one.\n"
            "Line two of summary.\n"
            "WHY_IT_MATTERS: Important reason.\n"
            "REMEDIATION_HINT: Step 1.\n"
            "Step 2."
        )
        sections = _parse_ai_response(text)
        assert sections["summary"] == "Line one. Line two of summary."
        assert sections["remediation_hint"] == "Step 1. Step 2."


class TestAIExplainFinding:
    def test_returns_cached_result(self, tmp_path: Path) -> None:
        finding = _make_finding()
        cache_key = _compute_cache_key(finding)

        from datetime import UTC, datetime

        cached_data = {
            "control_id": "1.1",
            "framework": "cis_gcp",
            "title": "Cached title",
            "severity": "HIGH",
            "status": "FAIL",
            "summary": "Cached summary",
            "why_it_matters": "Cached why",
            "severity_rationale": "Severity: HIGH.",
            "remediation_hint": "Cached fix",
            "resource_address": "google_compute_instance.web",
            "ai_generated": True,
            "model": _AI_MODEL,
            "cached": False,
            "_cached_at": datetime.now(UTC).isoformat(),
        }
        cache_dir = tmp_path / "explain"
        cache_dir.mkdir(parents=True)
        (cache_dir / f"{cache_key}.json").write_text(
            json.dumps(cached_data), encoding="utf-8"
        )

        result = ai_explain_finding(finding, api_key="sk-test", cache_dir=cache_dir)
        assert result.cached is True
        assert result.summary == "Cached summary"

    def test_api_error_raises(self, tmp_path: Path) -> None:
        finding = _make_finding()

        mock_response = MagicMock()
        mock_response.status_code = 401
        mock_response.text = "Unauthorized"
        mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
            "401",
            request=MagicMock(),
            response=mock_response,
        )

        with (
            patch("httpx.post", return_value=mock_response),
            pytest.raises(AIExplainAPIError),
        ):
            ai_explain_finding(
                finding,
                api_key="sk-bad",
                cache_dir=tmp_path / "explain",
            )

    def test_successful_api_call(self, tmp_path: Path) -> None:
        finding = _make_finding()
        cache_dir = tmp_path / "explain"

        ai_response_text = (
            "SUMMARY: AI-generated summary.\n"
            "WHY_IT_MATTERS: AI security insight.\n"
            "REMEDIATION_HINT: AI remediation steps."
        )
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.raise_for_status = MagicMock()
        mock_response.json.return_value = {
            "content": [{"type": "text", "text": ai_response_text}],
        }

        with patch("httpx.post", return_value=mock_response):
            result = ai_explain_finding(
                finding,
                api_key="sk-test",
                cache_dir=cache_dir,
            )

        assert result.ai_generated is True
        assert result.model == _AI_MODEL
        assert result.cached is False
        assert "AI-generated summary" in result.summary
        # Verify cache was written
        assert (cache_dir / f"{_compute_cache_key(finding)}.json").exists()


class TestCacheTTL:
    def test_ttl_is_30_days(self) -> None:
        assert EXPLAIN_CACHE_TTL_SECONDS == 30 * 24 * 3600
