# #region test_clean_release_cli [C:2] [TYPE Module]
# @RELATION  BINDS_TO -> SrcRoot
# @PURPOSE: Smoke tests for the redesigned clean release CLI.
# @LAYER Domain

"""Smoke tests for the redesigned clean release CLI commands."""

from datetime import UTC, datetime
from types import SimpleNamespace
from uuid import uuid4

from src.dependencies import get_clean_release_repository, get_config_manager
from src.models.clean_release import (
    CleanPolicySnapshot,
    ComplianceReport,
    ReleaseCandidate,
    SourceRegistrySnapshot,
)
from src.scripts.clean_release_cli import main as cli_main
from src.services.clean_release.enums import CandidateStatus, ComplianceDecision


# #region test_cli_candidate_register_scaffold [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> test_clean_release_cli
# @PURPOSE: Verify candidate-register command exits successfully for valid required arguments.
def test_cli_candidate_register_scaffold() -> None:
    """Candidate register CLI command smoke test."""
    exit_code = cli_main(
        [
            "candidate-register",
            "--candidate-id",
            "cli-candidate-1",
            "--version",
            "1.0.0",
            "--source-snapshot-ref",
            "git:sha123",
            "--created-by",
            "cli-test",
        ]
    )
    assert exit_code == 0


# #endregion test_cli_candidate_register_scaffold


# #region test_cli_manifest_build_scaffold [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> test_clean_release_cli
# @PURPOSE: Verify candidate-register/artifact-import/manifest-build smoke path succeeds end-to-end.
def test_cli_manifest_build_scaffold() -> None:
    """Manifest build CLI command smoke test."""
    register_exit = cli_main(
        [
            "candidate-register",
            "--candidate-id",
            "cli-candidate-2",
            "--version",
            "1.0.0",
            "--source-snapshot-ref",
            "git:sha234",
            "--created-by",
            "cli-test",
        ]
    )
    assert register_exit == 0

    import_exit = cli_main(
        [
            "artifact-import",
            "--candidate-id",
            "cli-candidate-2",
            "--artifact-id",
            "artifact-2",
            "--path",
            "bin/app",
            "--sha256",
            "feedbeef",
            "--size",
            "24",
        ]
    )
    assert import_exit == 0

    manifest_exit = cli_main(
        [
            "manifest-build",
            "--candidate-id",
            "cli-candidate-2",
            "--created-by",
            "cli-test",
        ]
    )
    assert manifest_exit == 0


# #endregion test_cli_manifest_build_scaffold


# #region test_cli_compliance_run_scaffold [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> test_clean_release_cli
# @PURPOSE: Verify compliance run/status/violations/report commands complete for prepared candidate.
def test_cli_compliance_run_scaffold() -> None:
    """Compliance CLI command smoke test for run/status/report/violations."""
    from unittest.mock import MagicMock, patch

    # get_config_manager() tries to open project_root/config.json which is a
    # directory, not a file. Mock the entire ConfigManager class so that both
    # the test's direct call and the CLI's internal lazy import use the mock.
    with patch("src.dependencies.ConfigManager") as MockConfigMgr:
        mock_cfg_mgr = MagicMock()
        mock_cfg = MagicMock()
        mock_cfg.settings = SimpleNamespace()
        mock_cfg_mgr.get_config.return_value = mock_cfg
        MockConfigMgr.return_value = mock_cfg_mgr

        repository = get_clean_release_repository()
        config_manager = get_config_manager()

    registry = SourceRegistrySnapshot(
        id="cli-registry",
        registry_id="trusted-registry",
        registry_version="1.0.0",
        allowed_hosts=["repo.internal.local"],
        allowed_schemes=["https"],
        allowed_source_types=["repo"],
        immutable=True,
    )
    policy = CleanPolicySnapshot(
        id="cli-policy",
        policy_id="trusted-policy",
        policy_version="1.0.0",
        content_json={"rules": []},
        registry_snapshot_id=registry.id,
        immutable=True,
    )
    repository.save_registry(registry)
    repository.save_policy(policy)

    config = config_manager.get_config()
    if getattr(config, "settings", None) is None:
        # @INVARIANT: SimpleNamespace substitutes for GlobalSettings — any field rename in GlobalSettings will silently not propagate here; re-verify on GlobalSettings schema changes.
        config.settings = SimpleNamespace()
    config.settings.clean_release = SimpleNamespace(
        active_policy_id=policy.id,
        active_registry_id=registry.id,
    )

    register_exit = cli_main(
        [
            "candidate-register",
            "--candidate-id",
            "cli-candidate-3",
            "--version",
            "1.0.0",
            "--source-snapshot-ref",
            "git:sha345",
            "--created-by",
            "cli-test",
        ]
    )
    assert register_exit == 0

    import_exit = cli_main(
        [
            "artifact-import",
            "--candidate-id",
            "cli-candidate-3",
            "--artifact-id",
            "artifact-1",
            "--path",
            "bin/app",
            "--sha256",
            "deadbeef",
            "--size",
            "42",
        ]
    )
    assert import_exit == 0

    manifest_exit = cli_main(
        [
            "manifest-build",
            "--candidate-id",
            "cli-candidate-3",
            "--created-by",
            "cli-test",
        ]
    )
    assert manifest_exit == 0

    run_exit = cli_main(
        [
            "compliance-run",
            "--candidate-id",
            "cli-candidate-3",
            "--actor",
            "cli-test",
            "--json",
        ]
    )
    assert run_exit == 0

    run_id = next(
        run.id
        for run in repository.check_runs.values()
        if run.candidate_id == "cli-candidate-3"
    )

    status_exit = cli_main(["compliance-status", "--run-id", run_id, "--json"])
    assert status_exit == 0

    violations_exit = cli_main(["compliance-violations", "--run-id", run_id, "--json"])
    assert violations_exit == 0

    report_exit = cli_main(["compliance-report", "--run-id", run_id, "--json"])
    assert report_exit == 0


# #endregion test_cli_compliance_run_scaffold


# #region test_cli_release_gate_commands_scaffold [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> test_clean_release_cli
# @PURPOSE: Verify approve/reject/publish/revoke release-gate commands execute with valid fixtures.
def test_cli_release_gate_commands_scaffold() -> None:
    """Release gate CLI smoke test for approve/reject/publish/revoke commands."""
    repository = get_clean_release_repository()

    approved_candidate_id = f"cli-release-approved-{uuid4()}"
    rejected_candidate_id = f"cli-release-rejected-{uuid4()}"
    approved_report_id = f"CCR-cli-release-approved-{uuid4()}"
    rejected_report_id = f"CCR-cli-release-rejected-{uuid4()}"

    repository.save_candidate(
        ReleaseCandidate(
            id=approved_candidate_id,
            version="1.0.0",
            source_snapshot_ref="git:sha-approved",
            created_by="cli-test",
            created_at=datetime.now(UTC),
            status=CandidateStatus.CHECK_PASSED.value,
        )
    )
    repository.save_candidate(
        ReleaseCandidate(
            id=rejected_candidate_id,
            version="1.0.0",
            source_snapshot_ref="git:sha-rejected",
            created_by="cli-test",
            created_at=datetime.now(UTC),
            status=CandidateStatus.CHECK_PASSED.value,
        )
    )
    repository.save_report(
        ComplianceReport(
            id=approved_report_id,
            run_id=f"run-{uuid4()}",
            candidate_id=approved_candidate_id,
            final_status=ComplianceDecision.PASSED.value,
            summary_json={
                "operator_summary": "ok",
                "violations_count": 0,
                "blocking_violations_count": 0,
            },
            generated_at=datetime.now(UTC),
            immutable=True,
        )
    )
    repository.save_report(
        ComplianceReport(
            id=rejected_report_id,
            run_id=f"run-{uuid4()}",
            candidate_id=rejected_candidate_id,
            final_status=ComplianceDecision.PASSED.value,
            summary_json={
                "operator_summary": "ok",
                "violations_count": 0,
                "blocking_violations_count": 0,
            },
            generated_at=datetime.now(UTC),
            immutable=True,
        )
    )

    approve_exit = cli_main(
        [
            "approve",
            "--candidate-id",
            approved_candidate_id,
            "--report-id",
            approved_report_id,
            "--actor",
            "cli-test",
            "--comment",
            "approve candidate",
            "--json",
        ]
    )
    assert approve_exit == 0

    reject_exit = cli_main(
        [
            "reject",
            "--candidate-id",
            rejected_candidate_id,
            "--report-id",
            rejected_report_id,
            "--actor",
            "cli-test",
            "--comment",
            "reject candidate",
            "--json",
        ]
    )
    assert reject_exit == 0

    publish_exit = cli_main(
        [
            "publish",
            "--candidate-id",
            approved_candidate_id,
            "--report-id",
            approved_report_id,
            "--actor",
            "cli-test",
            "--target-channel",
            "stable",
            "--publication-ref",
            "rel-cli-001",
            "--json",
        ]
    )
    assert publish_exit == 0

    publication_records = getattr(repository, "publication_records", [])
    assert publication_records
    publication_id = publication_records[-1].id

    revoke_exit = cli_main(
        [
            "revoke",
            "--publication-id",
            publication_id,
            "--actor",
            "cli-test",
            "--comment",
            "rollback",
            "--json",
        ]
    )
    assert revoke_exit == 0


# #endregion test_cli_release_gate_commands_scaffold


# #region test_cli_error_branches [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> test_clean_release_cli
# @PURPOSE: Verify error-handling branches return expected exit codes and error messages.
def test_cli_duplicate_candidate() -> None:
    """Registering an existing candidate returns exit code 1."""
    cli_main(["candidate-register", "--candidate-id", "dup-cand", "--version", "1.0", "--source-snapshot-ref", "git:x"])
    exit_code = cli_main(["candidate-register", "--candidate-id", "dup-cand", "--version", "2.0", "--source-snapshot-ref", "git:y"])
    assert exit_code == 1


def test_cli_artifact_import_candidate_not_found() -> None:
    """Importing artifact for non-existent candidate returns exit code 1."""
    exit_code = cli_main(["artifact-import", "--candidate-id", "no-such-cand", "--artifact-id", "a1", "--path", "x", "--sha256", "0" * 64, "--size", "1"])
    assert exit_code == 1


def test_cli_manifest_build_value_error(monkeypatch) -> None:
    """Manifest build on unprepared candidate returns exit code 1."""
    from src.dependencies import get_clean_release_repository
    repository = get_clean_release_repository()

    # Register candidate but skip artifact import — manifest build will fail
    cli_main(["candidate-register", "--candidate-id", "no-artifacts", "--version", "1.0", "--source-snapshot-ref", "git:x", "--created-by", "test"])

    # Manually set status to DRAFT so manifest build fails
    candidate = repository.get_candidate("no-artifacts")
    if candidate:
        candidate.status = "DRAFT"
        repository.save_candidate(candidate)

    exit_code = cli_main(["manifest-build", "--candidate-id", "no-artifacts", "--created-by", "test"])
    assert exit_code == 1


def test_cli_compliance_run_exception(monkeypatch) -> None:
    """Compliance run when config is broken returns exit code 2."""
    from unittest.mock import patch, MagicMock
    import json

    with patch("src.dependencies.ConfigManager") as MockCfgMgr:
        mock_mgr = MagicMock()
        mock_mgr.get_config.side_effect = RuntimeError("config broken")
        MockCfgMgr.return_value = mock_mgr

        exit_code = cli_main(["compliance-run", "--candidate-id", "no-such", "--actor", "test", "--json"])
        assert exit_code == 2


def test_cli_compliance_status_not_found() -> None:
    """Status query for non-existent run returns exit code 2."""
    exit_code = cli_main(["compliance-status", "--run-id", "no-such-run", "--json"])
    assert exit_code == 2


def test_cli_compliance_report_not_found() -> None:
    """Report query for non-existent run returns exit code 2."""
    exit_code = cli_main(["compliance-report", "--run-id", "no-such-run", "--json"])
    assert exit_code == 2


def test_cli_compliance_report_missing() -> None:
    """Report query when run exists but no report returns exit code 2."""
    from src.dependencies import get_clean_release_repository
    from unittest.mock import MagicMock

    repository = get_clean_release_repository()
    run = MagicMock()
    run.id = "cli-run-no-report"
    run.candidate_id = "cli-cand-nr"
    run.status = "PENDING"
    run.final_status = "PENDING"
    repository.save_check_run(run)

    exit_code = cli_main(["compliance-report", "--run-id", "cli-run-no-report", "--json"])
    assert exit_code == 2


def test_cli_compliance_violations_not_found() -> None:
    """Violations query for non-existent run returns exit code 2."""
    exit_code = cli_main(["compliance-violations", "--run-id", "no-such-run", "--json"])
    assert exit_code == 2


def test_cli_approve_exception(monkeypatch) -> None:
    """Approve with approval service failure returns exit code 2."""
    from unittest.mock import patch

    with patch("src.services.clean_release.approval_service.approve_candidate", side_effect=RuntimeError("approval failed")):
        exit_code = cli_main(["approve", "--candidate-id", "x", "--report-id", "y", "--actor", "test", "--json"])
        assert exit_code == 2


def test_cli_reject_exception(monkeypatch) -> None:
    """Reject with approval service failure returns exit code 2."""
    from unittest.mock import patch

    with patch("src.services.clean_release.approval_service.reject_candidate", side_effect=RuntimeError("reject failed")):
        exit_code = cli_main(["reject", "--candidate-id", "x", "--report-id", "y", "--actor", "test", "--json"])
        assert exit_code == 2


def test_cli_publish_exception(monkeypatch) -> None:
    """Publish with publication service failure returns exit code 2."""
    from unittest.mock import patch

    with patch("src.services.clean_release.publication_service.publish_candidate", side_effect=RuntimeError("publish failed")):
        exit_code = cli_main(["publish", "--candidate-id", "x", "--report-id", "y", "--actor", "test", "--target-channel", "stable", "--json"])
        assert exit_code == 2


def test_cli_revoke_exception(monkeypatch) -> None:
    """Revoke with publication service failure returns exit code 2."""
    from unittest.mock import patch

    with patch("src.services.clean_release.publication_service.revoke_publication", side_effect=RuntimeError("revoke failed")):
        exit_code = cli_main(["revoke", "--publication-id", "x", "--actor", "test", "--json"])
        assert exit_code == 2


def test_cli_unknown_command() -> None:
    """Unknown command raises SystemExit with code 2."""
    import pytest
    with pytest.raises(SystemExit) as exc_info:
        cli_main(["bogus-command"])
    assert exc_info.value.code == 2


# #endregion test_cli_error_branches


# #region test_to_payload [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> test_clean_release_cli
# @PURPOSE: Verify _to_payload serialization for datetime, date, dict, list, tuple, Pydantic, SQLAlchemy, and unsupported types.
def test_to_payload_datetime() -> None:
    """_to_payload handles datetime.isoformat()."""
    from datetime import UTC, datetime, date
    from src.scripts.clean_release_cli import _to_payload
    from unittest.mock import MagicMock

    obj = MagicMock()
    obj.model_dump.return_value = {"ts": datetime(2024, 1, 15, 10, 30, 0, tzinfo=UTC)}
    result = _to_payload(obj)
    assert result["ts"] == "2024-01-15T10:30:00+00:00"


def test_to_payload_date() -> None:
    """_to_payload handles date.isoformat()."""
    from datetime import date
    from src.scripts.clean_release_cli import _to_payload
    from unittest.mock import MagicMock

    obj = MagicMock()
    obj.model_dump.return_value = {"d": date(2024, 6, 15)}
    result = _to_payload(obj)
    assert result["d"] == "2024-06-15"


def test_to_payload_dict_nested() -> None:
    """_to_payload recursively normalizes nested dicts."""
    from src.scripts.clean_release_cli import _to_payload
    from unittest.mock import MagicMock

    obj = MagicMock()
    obj.model_dump.return_value = {"nested": {"key": 42, "flag": True}}
    result = _to_payload(obj)
    assert result["nested"] == {"key": 42, "flag": True}


def test_to_payload_list() -> None:
    """_to_payload handles list items."""
    from src.scripts.clean_release_cli import _to_payload
    from unittest.mock import MagicMock

    obj = MagicMock()
    obj.model_dump.return_value = {"items": ["a", "b", "c"]}
    result = _to_payload(obj)
    assert result["items"] == ["a", "b", "c"]


def test_to_payload_tuple() -> None:
    """_to_payload converts tuple to list."""
    from src.scripts.clean_release_cli import _to_payload
    from unittest.mock import MagicMock

    obj = MagicMock()
    obj.model_dump.return_value = {"coords": (1, 2)}
    result = _to_payload(obj)
    assert result["coords"] == [1, 2]


def test_to_payload_sqlalchemy_model() -> None:
    """_to_payload handles SQLAlchemy model with __table__."""
    from src.scripts.clean_release_cli import _to_payload
    from unittest.mock import MagicMock

    # Use a real class (not Mock) so model_dump is not auto-created
    class _FakeModel:
        pass

    col1 = MagicMock()
    col1.name = "id"
    col2 = MagicMock()
    col2.name = "name"
    _FakeModel.__table__ = MagicMock(columns=[col1, col2])

    obj = _FakeModel()
    obj.id = 1
    obj.name = "test"

    result = _to_payload(obj)
    assert result["id"] == 1
    assert result["name"] == "test"


def test_to_payload_unsupported_type() -> None:
    """_to_payload raises TypeError for unsupported types."""
    from src.scripts.clean_release_cli import _to_payload

    import pytest
    with pytest.raises(TypeError, match="unsupported payload type"):
        _to_payload(42)


# #endregion test_to_payload
# #endregion test_clean_release_cli
