"""Tests for complyform explain CLI command."""

from __future__ import annotations

import json
from unittest.mock import patch

from typer.testing import CliRunner

from complyform.cli.main import app
from complyform.core.assessor.control_matcher import Finding

runner = CliRunner()


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)


def _mock_explanation(**overrides: object) -> object:
    from complyform.core.explainer.models import Explanation

    defaults = {
        "control_id": "1.1",
        "framework": "cis_gcp",
        "title": "Test control",
        "severity": "HIGH",
        "status": "FAIL",
        "summary": "Test summary",
        "why_it_matters": "Test why",
        "severity_rationale": "Severity: HIGH.",
        "remediation_hint": "Test fix",
        "resource_address": "google_compute_instance.web",
    }
    defaults.update(overrides)
    return Explanation.model_validate(defaults)


class TestExplainCmdNoCache:
    def test_no_assessment_exits_nonzero(self) -> None:
        with patch(
            "complyform.core.cache.ScanCache.latest",
            return_value=None,
        ):
            result = runner.invoke(app, ["explain", "1.1"])
            assert result.exit_code != 0


class TestExplainCmdTerminal:
    def test_terminal_output_contains_control_id(self) -> None:
        finding = _make_finding()
        with (
            patch(
                "complyform.cli.explain_cmd._resolve_finding",
                return_value=finding,
            ),
            patch(
                "complyform.cli.explain_cmd._generate_explanation",
                return_value=_mock_explanation(),
            ),
        ):
            result = runner.invoke(app, ["explain", "1.1"])
            assert result.exit_code == 0
            assert "1.1" in result.output


class TestExplainCmdJSON:
    def test_json_envelope(self) -> None:
        finding = _make_finding()
        with (
            patch(
                "complyform.cli.explain_cmd._resolve_finding",
                return_value=finding,
            ),
            patch(
                "complyform.cli.explain_cmd._generate_explanation",
                return_value=_mock_explanation(),
            ),
        ):
            result = runner.invoke(
                app,
                ["explain", "1.1", "--format", "json"],
            )
            assert result.exit_code == 0
            lines = result.output.strip().splitlines()
            json_start = next(
                i for i, line in enumerate(lines) if line.strip().startswith("{")
            )
            envelope = json.loads("\n".join(lines[json_start:]))
            assert envelope["schema_version"] == 1
            assert envelope["command"] == "explain"
            assert envelope["target"] == "1.1"
            assert envelope["framework"] == "cis_gcp"
            assert envelope["mode"] == "deterministic"
            expl = envelope["explanation"]
            assert "summary" in expl
            assert "why_it_matters" in expl
            assert "severity_rationale" in expl
            assert "remediation_hint" in expl
            assert "ai_generated" in expl
            assert "model" in expl
            # Ensure only spec fields in explanation
            assert len(expl) == 6


class TestExplainAmbiguous:
    def test_ambiguous_control_requires_framework(
        self,
    ) -> None:
        findings = [
            _make_finding(framework="cis_gcp"),
            _make_finding(framework="soc2"),
        ]

        def mock_load_findings(
            _meta: object,
        ) -> list[Finding]:
            return findings

        with (
            patch(
                "complyform.core.cache.ScanCache.latest",
                return_value=([], {}),
            ),
            patch(
                "complyform.cli.explain_cmd._load_cached_findings",
                side_effect=mock_load_findings,
            ),
        ):
            result = runner.invoke(app, ["explain", "1.1"])
            assert result.exit_code != 0


class TestExplainAIFlag:
    def test_ai_flag_without_tier_blocked(self) -> None:
        finding = _make_finding()
        with patch(
            "complyform.cli.explain_cmd._resolve_finding",
            return_value=finding,
        ):
            result = runner.invoke(app, ["explain", "1.1", "--ai"])
            # Should fail due to tier check (community has no ai_explain)
            assert result.exit_code != 0


class TestExplainFrameworksFlag:
    def test_frameworks_flag_accepted(self) -> None:
        finding = _make_finding()
        with (
            patch(
                "complyform.cli.explain_cmd._resolve_finding",
                return_value=finding,
            ),
            patch(
                "complyform.cli.explain_cmd._generate_explanation",
                return_value=_mock_explanation(),
            ),
        ):
            result = runner.invoke(
                app,
                [
                    "explain",
                    "1.1",
                    "--frameworks",
                    "cis_gcp",
                ],
            )
            assert result.exit_code == 0


class TestExplainAIFallback:
    def test_api_error_falls_back_to_deterministic(
        self,
    ) -> None:
        from complyform.core.errors import AIExplainAPIError

        finding = _make_finding()
        with (
            patch(
                "complyform.cli.explain_cmd._resolve_finding",
                return_value=finding,
            ),
            patch(
                "complyform.core.auth.license.LicenseManager.resolve_tier_info",
            ),
            patch(
                "complyform.core.auth.tier_enforcement.enforce_with_refresh",
            ),
            patch(
                "complyform.core.explainer.ai_enhanced.resolve_api_key",
                return_value="sk-test",
            ),
            patch(
                "complyform.core.explainer.ai_enhanced.ai_explain_finding",
                side_effect=AIExplainAPIError(500, "Internal Server Error"),
            ),
        ):
            result = runner.invoke(
                app,
                [
                    "explain",
                    "1.1",
                    "--ai",
                    "--format",
                    "json",
                ],
            )
            assert result.exit_code == 0
            assert "Warning" in result.output
            # Find the JSON block — skip warning line(s)
            lines = result.output.strip().splitlines()
            json_start = next(
                i for i, line in enumerate(lines) if line.strip().startswith("{")
            )
            # Find the end of the JSON block
            json_end = len(lines)
            for i in range(len(lines) - 1, json_start, -1):
                if lines[i].strip() == "}":
                    json_end = i + 1
                    break
            envelope = json.loads("\n".join(lines[json_start:json_end]))
            assert envelope["explanation"]["ai_generated"] is False
