"""Tests for complyform.cli.frameworks_cmd — full coverage."""

from __future__ import annotations

import json

from typer.testing import CliRunner

from complyform.cli.main import app

runner = CliRunner()


# -------------------------------------------------------------------
# frameworks list
# -------------------------------------------------------------------


class TestFrameworksList:
    def test_list_all(self) -> None:
        result = runner.invoke(app, ["frameworks", "list"])
        assert result.exit_code == 0
        assert "Compliance Frameworks" in result.output
        assert "53 frameworks" in result.output

    def test_list_filter_tier(self) -> None:
        result = runner.invoke(app, ["frameworks", "list", "--tier", "community"])
        assert result.exit_code == 0
        assert "soc2" in result.output
        assert "4 frameworks" in result.output

    def test_list_filter_cloud(self) -> None:
        result = runner.invoke(app, ["frameworks", "list", "--cloud", "gcp"])
        assert result.exit_code == 0
        assert "cis_gcp" in result.output

    def test_list_filter_category(self) -> None:
        result = runner.invoke(app, ["frameworks", "list", "--category", "cis"])
        assert result.exit_code == 0
        assert "cis_gcp" in result.output

    def test_list_search(self) -> None:
        result = runner.invoke(app, ["frameworks", "list", "--search", "soc"])
        assert result.exit_code == 0
        assert "soc2" in result.output

    def test_list_json(self) -> None:
        result = runner.invoke(app, ["frameworks", "list", "--format", "json"])
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert len(data) == 53
        assert data[0]["id"] == "soc2"

    def test_list_json_filtered(self) -> None:
        result = runner.invoke(
            app,
            ["frameworks", "list", "--tier", "community", "--format", "json"],
        )
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert len(data) == 4

    def test_list_empty_filter(self) -> None:
        """Filter that matches nothing still succeeds with 0 frameworks."""
        result = runner.invoke(
            app,
            ["frameworks", "list", "--tier", "nonexistent"],
        )
        assert result.exit_code == 0
        assert "0 frameworks" in result.output

    def test_list_quiet_mode(self) -> None:
        result = runner.invoke(app, ["--quiet", "frameworks", "list"])
        assert result.exit_code == 0
        # quiet suppresses table output
        assert "Compliance Frameworks" not in result.output

    def test_list_search_json(self) -> None:
        """Search combined with JSON format."""
        result = runner.invoke(
            app,
            ["frameworks", "list", "--search", "cis", "--format", "json"],
        )
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert all("cis" in f["id"].lower() or "CIS" in f["name"] for f in data)


# -------------------------------------------------------------------
# frameworks info
# -------------------------------------------------------------------


class TestFrameworksInfo:
    def test_info_soc2(self) -> None:
        result = runner.invoke(app, ["frameworks", "info", "soc2"])
        assert result.exit_code == 0
        assert "SOC 2" in result.output
        assert "AICPA" in result.output

    def test_info_shows_description(self) -> None:
        result = runner.invoke(app, ["frameworks", "info", "soc2"])
        assert result.exit_code == 0
        # The panel shows framework details
        assert "Framework: soc2" in result.output

    def test_info_shows_sections_table(self) -> None:
        result = runner.invoke(app, ["frameworks", "info", "soc2"])
        assert result.exit_code == 0
        assert "Sections" in result.output

    def test_info_json(self) -> None:
        result = runner.invoke(app, ["frameworks", "info", "soc2", "--format", "json"])
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert data["id"] == "soc2"
        assert "controls" in data
        assert len(data["controls"]) >= 20

    def test_info_json_with_controls_load_failure(self) -> None:
        """When controls file can't load, JSON still returns empty controls."""
        result = runner.invoke(
            app,
            ["frameworks", "info", "iso27001", "--format", "json"],
        )
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert data["id"] == "iso27001"
        assert data["controls"] == []

    def test_info_terminal_controls_load_failure(self) -> None:
        """When controls file can't load, terminal shows fallback message."""
        result = runner.invoke(app, ["frameworks", "info", "iso27001"])
        assert result.exit_code == 0
        assert "Controls file not available" in result.output

    def test_info_not_found(self) -> None:
        result = runner.invoke(app, ["frameworks", "info", "nonexistent"])
        assert result.exit_code != 0

    def test_info_quiet_mode(self) -> None:
        result = runner.invoke(app, ["--quiet", "frameworks", "info", "soc2"])
        assert result.exit_code == 0
        # quiet suppresses panel output
        assert "Framework: soc2" not in result.output


# -------------------------------------------------------------------
# frameworks diff
# -------------------------------------------------------------------


class TestFrameworksDiff:
    def test_diff_different(self) -> None:
        result = runner.invoke(app, ["frameworks", "diff", "soc2", "cis_gcp"])
        assert result.exit_code == 0
        assert "soc2" in result.output
        assert "cis_gcp" in result.output
        assert "Comparison" in result.output

    def test_diff_identical(self) -> None:
        """Same framework on both sides — all fields match (green)."""
        result = runner.invoke(app, ["frameworks", "diff", "soc2", "soc2"])
        assert result.exit_code == 0
        assert "Comparison: soc2 vs soc2" in result.output

    def test_diff_with_cross_mappings(self) -> None:
        """Diff between frameworks that have cross-mappings."""
        result = runner.invoke(app, ["frameworks", "diff", "soc2", "cis_gcp"])
        assert result.exit_code == 0
        assert "cross-mappings found" in result.output

    def test_diff_no_cross_mappings(self) -> None:
        """Diff between frameworks with no cross-mappings."""
        from unittest.mock import patch

        with patch(
            "complyform.core.profiles.cross_mappings.get_all",
            return_value=[],
        ):
            result = runner.invoke(app, ["frameworks", "diff", "soc2", "cis_gcp"])
        assert result.exit_code == 0
        # No cross-mappings message should NOT appear
        assert "cross-mappings found" not in result.output

    def test_diff_not_found(self) -> None:
        result = runner.invoke(app, ["frameworks", "diff", "soc2", "nonexistent"])
        assert result.exit_code != 0

    def test_diff_json_not_supported(self) -> None:
        """Diff doesn't have --format flag — just renders terminal."""
        result = runner.invoke(app, ["frameworks", "diff", "soc2", "cis_gcp"])
        assert result.exit_code == 0


# -------------------------------------------------------------------
# frameworks (no subcommand)
# -------------------------------------------------------------------


class TestFrameworksNoArgs:
    def test_no_args_shows_help(self) -> None:
        result = runner.invoke(app, ["frameworks"])
        assert "list" in result.output
        assert "info" in result.output
        assert "diff" in result.output


# -------------------------------------------------------------------
# Non-Console guard coverage (lines 74, 139, 200)
# -------------------------------------------------------------------


class TestNonConsoleGuard:
    """Cover early-return when console is not a Rich Console instance."""

    def _make_ctx(self) -> None:
        """Stub AppContext with a non-Console console attribute."""
        from dataclasses import dataclass

        @dataclass
        class FakeCtx:
            verbose: bool = False
            quiet: bool = False
            no_progress: bool = False
            console: object = "not_a_console"

        self._ctx = FakeCtx()  # type: ignore[assignment]

    def test_list_non_console(self) -> None:
        """frameworks_list returns silently with non-Console console."""
        self._make_ctx()
        from unittest.mock import MagicMock

        ctx = MagicMock()
        ctx.obj = self._ctx
        from complyform.cli.frameworks_cmd import frameworks_list

        # Call directly — no Rich output, just returns
        frameworks_list(ctx, format="terminal")

    def test_info_non_console(self) -> None:
        self._make_ctx()
        from unittest.mock import MagicMock

        ctx = MagicMock()
        ctx.obj = self._ctx
        from complyform.cli.frameworks_cmd import frameworks_info

        frameworks_info(ctx, framework_id="soc2", format="terminal")

    def test_diff_non_console(self) -> None:
        self._make_ctx()
        from unittest.mock import MagicMock

        ctx = MagicMock()
        ctx.obj = self._ctx
        from complyform.cli.frameworks_cmd import frameworks_diff

        frameworks_diff(ctx, framework_a="soc2", framework_b="cis_gcp")
