# #region TestFacadeAdapter [C:3] [TYPE Module] [SEMANTICS test,clean-release,facade,tui,adapter]
# @BRIEF Tests for TuiFacadeAdapter and CleanReleaseTUI logic methods (non-rendering).
# @RELATION BINDS_TO -> [CleanReleaseTuiScript]
# @TEST_EDGE: facade_build_config_no_policy -> Raises ValueError
# @TEST_EDGE: facade_run_compliance_no_manifests -> Raises ValueError
# @TEST_EDGE: facade_approve_latest_no_reports -> Raises ValueError
# @TEST_EDGE: facade_publish_latest_no_reports -> Raises ValueError
# @TEST_EDGE: resolve_candidate_id_by_env -> Uses CLEAN_TUI_CANDIDATE_ID
# @TEST_EDGE: resolve_candidate_id_fallback -> Uses first candidate
# @TEST_EDGE: resolve_candidate_id_empty -> Returns ""
# @TEST_EDGE: tui_run_checks_error -> FAILED status on exception
# @TEST_EDGE: tui_approve_no_report -> Disabled message when no report_id
# @TEST_EDGE: tui_publish_no_report -> Disabled message when no report_id
# @TEST_EDGE: tui_approve_error -> Error stored on facade failure
# @TEST_EDGE: tui_publish_error -> Error stored on facade failure
# @TEST_EDGE: tui_build_manifest_error -> Error stored on facade failure
# @TEST_EDGE: bootstrap_real_no_path -> Returns early
import os
import pytest
from unittest.mock import MagicMock, patch, PropertyMock
from types import SimpleNamespace

from src.models.clean_release import (
    CheckFinalStatus, CheckStageStatus, ComplianceViolation,
    ReleaseCandidate, CleanProfilePolicy, ProfileType,
    ResourceSourceRegistry, ResourceSourceEntry, RegistryStatus,
    ReleaseCandidateStatus, CandidateArtifact,
)
from src.services.clean_release.enums import CandidateStatus
from src.scripts.clean_release_tui import TuiFacadeAdapter, CleanReleaseTUI


# =============================================================================
# TuiFacadeAdapter tests (no curses required)
# =============================================================================

class TestTuiFacadeAdapterBuildConfig:
    """Verify _build_config_manager."""

    def test_build_config_with_policy(self):
        """Happy: policy found returns config with active_policy_id."""
        repo = MagicMock()
        policy = MagicMock()
        policy.id = "POL-1"
        policy.registry_snapshot_id = "REG-1"
        repo.get_active_policy.return_value = policy

        adapter = TuiFacadeAdapter(repo)
        cm = adapter._build_config_manager()
        config = cm.get_config()
        assert config.settings.clean_release.active_policy_id == "POL-1"
        assert config.settings.clean_release.active_registry_id == "REG-1"

    def test_build_config_no_policy_raises(self):
        """Edge: no active policy raises ValueError."""
        repo = MagicMock()
        repo.get_active_policy.return_value = None

        adapter = TuiFacadeAdapter(repo)
        with pytest.raises(ValueError, match="Active policy not found"):
            adapter._build_config_manager()


class TestTuiFacadeAdapterRunCompliance:
    """Verify run_compliance."""

    def test_run_compliance_with_manifests(self):
        """Happy: manifests found, executes compliance."""
        repo = MagicMock()
        policy = MagicMock()
        policy.id = "POL-1"
        policy.registry_snapshot_id = "REG-1"
        repo.get_active_policy.return_value = policy

        manifest_old = MagicMock()
        manifest_old.manifest_version = 1
        manifest_new = MagicMock()
        manifest_new.manifest_version = 2
        repo.get_manifests_by_candidate.return_value = [manifest_old, manifest_new]

        adapter = TuiFacadeAdapter(repo)
        with patch('src.scripts.clean_release_tui.ComplianceExecutionService') as MockService:
            mock_instance = MockService.return_value
            mock_instance.execute_run.return_value = "compliance_result"
            result = adapter.run_compliance(candidate_id="cand-1", actor="test")

        repo.get_manifests_by_candidate.assert_called_once_with("cand-1")
        assert result == "compliance_result"

    def test_run_compliance_no_manifests_raises(self):
        """Edge: no manifests raises ValueError."""
        repo = MagicMock()
        repo.get_manifests_by_candidate.return_value = []

        adapter = TuiFacadeAdapter(repo)
        with pytest.raises(ValueError, match="Manifest required before compliance run"):
            adapter.run_compliance(candidate_id="cand-1", actor="test")


class TestTuiFacadeAdapterApproveLatest:
    """Verify approve_latest."""

    def test_approve_with_reports(self):
        """Happy: reports found, approves."""
        repo = MagicMock()
        r1 = MagicMock()
        r1.candidate_id = "cand-1"
        r1.generated_at = "2024-01-01T00:00:00"
        r2 = MagicMock()
        r2.candidate_id = "cand-1"
        r2.generated_at = "2024-06-01T00:00:00"
        repo.reports = {"r1": r1, "r2": r2}

        adapter = TuiFacadeAdapter(repo)
        with patch('src.scripts.clean_release_tui.approve_candidate') as mock_approve:
            adapter.approve_latest(candidate_id="cand-1", actor="test")
            mock_approve.assert_called_once()

    def test_approve_no_reports_raises(self):
        """Edge: no reports raises ValueError."""
        repo = MagicMock()
        repo.reports = {}

        adapter = TuiFacadeAdapter(repo)
        with pytest.raises(ValueError, match="No compliance report available for approval"):
            adapter.approve_latest(candidate_id="cand-1", actor="test")


class TestTuiFacadeAdapterPublishLatest:
    """Verify publish_latest."""

    def test_publish_with_reports(self):
        """Happy: reports found, publishes."""
        repo = MagicMock()
        r1 = MagicMock()
        r1.candidate_id = "cand-1"
        r1.generated_at = "2024-01-01T00:00:00"
        repo.reports = {"r1": r1}

        adapter = TuiFacadeAdapter(repo)
        with patch('src.scripts.clean_release_tui.publish_candidate') as mock_publish:
            adapter.publish_latest(candidate_id="cand-1", actor="test")
            mock_publish.assert_called_once()

    def test_publish_no_reports_raises(self):
        """Edge: no reports raises ValueError."""
        repo = MagicMock()
        repo.reports = {}

        adapter = TuiFacadeAdapter(repo)
        with pytest.raises(ValueError, match="No compliance report available for publication"):
            adapter.publish_latest(candidate_id="cand-1", actor="test")


class TestTuiFacadeAdapterBuildManifest:
    """Verify build_manifest."""

    def test_build_manifest_delegates(self):
        """Happy: delegates to build_manifest_snapshot."""
        repo = MagicMock()
        adapter = TuiFacadeAdapter(repo)
        with patch('src.scripts.clean_release_tui.build_manifest_snapshot') as mock_build:
            adapter.build_manifest(candidate_id="cand-1", actor="test")
            mock_build.assert_called_once_with(
                repository=repo, candidate_id="cand-1", created_by="test",
            )


class TestTuiFacadeAdapterGetOverview:
    """Verify get_overview."""

    def test_get_overview_full(self):
        """Happy: all data present."""
        repo = MagicMock()
        candidate = MagicMock()
        candidate.id = "cand-1"
        repo.get_candidate.return_value = candidate

        manifest = MagicMock()
        manifest.manifest_version = 2
        repo.get_manifests_by_candidate.return_value = [manifest]

        run = MagicMock()
        run.candidate_id = "cand-1"
        run.requested_at = "2024-06-01T00:00:00"
        repo.check_runs = {"r1": run}

        report = MagicMock()
        report.run_id = run.id
        repo.reports = {"rep1": report}

        approval = MagicMock()
        approval.candidate_id = "cand-1"
        approval.decided_at = "2024-06-02T00:00:00"
        repo.approval_decisions = [approval]

        publication = MagicMock()
        publication.candidate_id = "cand-1"
        publication.published_at = "2024-06-03T00:00:00"
        repo.publication_records = [publication]

        policy = MagicMock()
        policy.internal_source_registry_ref = "REG-1"
        repo.get_active_policy.return_value = policy

        registry = MagicMock()
        repo.get_registry.return_value = registry

        adapter = TuiFacadeAdapter(repo)
        overview = adapter.get_overview(candidate_id="cand-1")

        assert overview["candidate"] is candidate
        assert overview["manifest"] is manifest
        assert overview["run"] is run
        assert overview["report"] is report
        assert overview["approval"] is approval
        assert overview["publication"] is publication
        assert overview["policy"] is policy
        assert overview["registry"] is registry

    def test_get_overview_empty(self):
        """Edge: no data at all, returns safe defaults."""
        repo = MagicMock()
        repo.get_candidate.return_value = None
        repo.get_manifests_by_candidate.return_value = []
        repo.check_runs = {}
        repo.reports = {}
        repo.get_active_policy.return_value = None

        adapter = TuiFacadeAdapter(repo)
        overview = adapter.get_overview(candidate_id="nonexistent")

        assert overview["candidate"] is None
        assert overview["manifest"] is None
        assert overview["run"] is None
        assert overview["report"] is None
        assert overview["approval"] is None
        assert overview["publication"] is None
        assert overview["policy"] is None
        assert overview["registry"] is None


# =============================================================================
# CleanReleaseTUI logic tests (curses mocked at module level)
# =============================================================================

@pytest.fixture
def mock_stdscr():
    stdscr = MagicMock()
    stdscr.getmaxyx.return_value = (40, 100)
    stdscr.getch.return_value = -1
    return stdscr


class TestResolveCandidateId:
    """Verify _resolve_candidate_id."""

    @patch("src.scripts.clean_release_tui.curses")
    def test_returns_env_var(self, mock_curses, mock_stdscr):
        """Edge: CLEAN_TUI_CANDIDATE_ID set returns env value."""
        with patch.dict(os.environ, {"CLEAN_TUI_CANDIDATE_ID": "env-cand"}, clear=False):
            app = CleanReleaseTUI(mock_stdscr)
            # After init the candidate_id is already resolved, verify it
            assert app.candidate_id == "env-cand"

    @patch("src.scripts.clean_release_tui.curses")
    def test_falls_back_to_first_candidate(self, mock_curses, mock_stdscr):
        """Edge: no env var, uses first candidate key."""
        app = CleanReleaseTUI(mock_stdscr)
        # Demo mode creates a candidate "2026.03.03-rc1"
        assert app.candidate_id == "2026.03.03-rc1"

    @patch("src.scripts.clean_release_tui.curses")
    def test_returns_empty_when_no_candidates(self, mock_curses):
        """Edge: no env var and no candidates returns empty string."""
        stdscr = MagicMock()
        stdscr.getmaxyx.return_value = (40, 100)
        # Inject a repo with no candidates
        from src.services.clean_release.repository import CleanReleaseRepository
        from src.scripts.clean_release_tui import TuiFacadeAdapter

        with patch.dict(os.environ, {"CLEAN_TUI_MODE": "real"}, clear=False):
            with patch("src.scripts.clean_release_tui.CleanReleaseTUI._bootstrap_real_repository"):
                app = CleanReleaseTUI(stdscr)
                # In real mode with no bootstrap, no candidates exist
                resolved = app._resolve_candidate_id()
                # _resolve_candidate_id is called during __init__, so the result is already in app.candidate_id
                assert app.candidate_id == ""


class TestTuiBuildManifestError:
    """Verify build_manifest error handling."""

    @patch("src.scripts.clean_release_tui.curses")
    def test_build_manifest_captures_error(self, mock_curses, mock_stdscr):
        """Edge: facade raises, error stored."""
        app = CleanReleaseTUI(mock_stdscr)
        # Replace facade with one that raises
        app.facade = MagicMock()
        app.facade.build_manifest.side_effect = ValueError("Build failed")

        app.build_manifest()
        assert "Build failed" in app.last_error


class TestTuiApproveError:
    """Verify approve_latest error handling."""

    @patch("src.scripts.clean_release_tui.curses")
    def test_approve_no_report_id(self, mock_curses, mock_stdscr):
        """Edge: no report_id sets disabled message."""
        app = CleanReleaseTUI(mock_stdscr)
        app.report_id = None
        app.approve_latest()
        assert "F8 disabled" in app.last_error

    @patch("src.scripts.clean_release_tui.curses")
    def test_approve_facade_error(self, mock_curses, mock_stdscr):
        """Edge: facade raises, error stored."""
        app = CleanReleaseTUI(mock_stdscr)
        app.report_id = "rep-1"
        app.facade = MagicMock()
        app.facade.approve_latest.side_effect = ValueError("Approve failed")

        app.approve_latest()
        assert "Approve failed" in app.last_error


class TestTuiPublishError:
    """Verify publish_latest error handling."""

    @patch("src.scripts.clean_release_tui.curses")
    def test_publish_no_report_id(self, mock_curses, mock_stdscr):
        """Edge: no report_id sets disabled message."""
        app = CleanReleaseTUI(mock_stdscr)
        app.report_id = None
        app.publish_latest()
        assert "F9 disabled" in app.last_error

    @patch("src.scripts.clean_release_tui.curses")
    def test_publish_facade_error(self, mock_curses, mock_stdscr):
        """Edge: facade raises, error stored."""
        app = CleanReleaseTUI(mock_stdscr)
        app.report_id = "rep-1"
        app.facade = MagicMock()
        app.facade.publish_latest.side_effect = ValueError("Publish failed")

        app.publish_latest()
        assert "Publish failed" in app.last_error


class TestTuiRunChecksError:
    """Verify run_checks error handling."""

    @patch("src.scripts.clean_release_tui.curses")
    def test_run_checks_facade_error(self, mock_curses, mock_stdscr):
        """Edge: facade raises during run_checks -> FAILED status."""
        app = CleanReleaseTUI(mock_stdscr)
        app.facade = MagicMock()
        app.facade.run_compliance.side_effect = RuntimeError("Compliance crashed")

        app.run_checks()
        assert app.status == CheckFinalStatus.FAILED
        assert "Compliance crashed" in app.last_error


class TestTuiClearHistory:
    """Verify clear_history resets state."""

    @patch("src.scripts.clean_release_tui.curses")
    def test_clear_history_resets_state(self, mock_curses, mock_stdscr):
        """Edge: after running checks, clear_history resets."""
        app = CleanReleaseTUI(mock_stdscr)
        app.status = CheckFinalStatus.BLOCKED
        app.report_id = "rep-1"
        app.violations_list = [MagicMock()]
        app.checks_progress = [{"stage": "test", "status": "FAIL"}]
        app.last_error = "some error"

        app.clear_history()
        assert app.status == "READY"
        assert app.report_id is None
        assert app.violations_list == []
        assert app.checks_progress == []
        assert app.last_error is None


# =============================================================================
# _bootstrap_real_repository tests
# =============================================================================

class TestBootstrapRealRepository:
    """Verify _bootstrap_real_repository."""

    @patch("src.scripts.clean_release_tui.curses")
    def test_no_bootstrap_path_returns_early(self, mock_curses, mock_stdscr):
        """Edge: CLEAN_TUI_BOOTSTRAP_JSON not set returns early."""
        from src.services.clean_release.repository import CleanReleaseRepository
        app = CleanReleaseTUI(mock_stdscr)
        repo = CleanReleaseRepository()

        app._bootstrap_real_repository(repo)
        # No bootstrap loaded -> no candidates
        assert len(repo.candidates) == 0

    @patch("src.scripts.clean_release_tui.curses")
    def test_bootstrap_with_valid_json(self, mock_curses, mock_stdscr, tmp_path):
        """Happy: valid bootstrap.json loads candidate, registry and policy."""
        import json
        bootstrap = tmp_path / "bootstrap.json"
        bootstrap.write_text(json.dumps({
            "candidate_id": "test-cand",
            "version": "1.0.0",
            "source_snapshot_ref": "git-ref",
            "created_by": "test",
            "allowed_hosts": ["repo.example.com"],
        }), encoding="utf-8")

        from src.services.clean_release.repository import CleanReleaseRepository
        app = CleanReleaseTUI(mock_stdscr)
        repo = CleanReleaseRepository()

        with patch.dict(os.environ, {
            "CLEAN_TUI_BOOTSTRAP_JSON": str(bootstrap),
        }, clear=False):
            app._bootstrap_real_repository(repo)

        assert repo.get_candidate("test-cand") is not None
        assert len(repo.registries) > 0
        assert len(repo.policies) > 0
        assert repo.get_candidate("test-cand").status == "DRAFT"

    @patch("src.scripts.clean_release_tui.curses")
    def test_bootstrap_with_artifacts_transitions_to_prepared(self, mock_curses, mock_stdscr, tmp_path):
        """Happy: artifacts imported transitions candidate to PREPARED."""
        import json
        bootstrap = tmp_path / "bootstrap.json"
        bootstrap.write_text(json.dumps({
            "candidate_id": "test-cand-2",
            "version": "2.0.0",
            "source_snapshot_ref": "git-ref-2",
            "created_by": "test",
            "allowed_hosts": [],
        }), encoding="utf-8")

        artifacts = tmp_path / "artifacts.json"
        artifacts.write_text(json.dumps({
            "artifacts": [{
                "id": "art-1", "path": "dist/file.tar.gz",
                "sha256": "abc123", "size": 512, "category": "core",
                "source_uri": "https://repo.example.com/file.tar.gz",
                "source_host": "repo.example.com",
            }]
        }), encoding="utf-8")

        from src.services.clean_release.repository import CleanReleaseRepository
        app = CleanReleaseTUI(mock_stdscr)
        repo = CleanReleaseRepository()

        with patch.dict(os.environ, {
            "CLEAN_TUI_BOOTSTRAP_JSON": str(bootstrap),
            "CLEAN_TUI_ARTIFACTS_JSON": str(artifacts),
        }, clear=False):
            app._bootstrap_real_repository(repo)

        assert repo.get_candidate("test-cand-2").status == "PREPARED"
        artifacts_list = repo.get_artifacts_by_candidate("test-cand-2")
        assert len(artifacts_list) == 1
        assert artifacts_list[0].detected_category == "core"

    @patch("src.scripts.clean_release_tui.curses")
    def test_bootstrap_with_entries_saves_policy(self, mock_curses, mock_stdscr, tmp_path):
        """Edge: entries present saves registry and policy."""
        import json
        bootstrap = tmp_path / "bootstrap.json"
        bootstrap.write_text(json.dumps({
            "candidate_id": "test-cand-3",
            "version": "3.0.0",
            "source_snapshot_ref": "git-ref-3",
            "created_by": "test",
            "allowed_hosts": ["repo.example.com"],
            "registry_id": "REG-TEST",
            "policy_id": "POL-TEST",
        }), encoding="utf-8")

        from src.services.clean_release.repository import CleanReleaseRepository
        app = CleanReleaseTUI(mock_stdscr)
        repo = CleanReleaseRepository()

        with patch.dict(os.environ, {
            "CLEAN_TUI_BOOTSTRAP_JSON": str(bootstrap),
        }, clear=False):
            app._bootstrap_real_repository(repo)

        assert repo.get_registry("REG-TEST") is not None
        policy = repo.get_policy("POL-TEST")
        assert policy is not None
# #endregion TestFacadeAdapter
