from __future__ import annotations

import argparse
import copy
import json
from pathlib import Path

import pytest

from scripts import build_telegram_visible_unseen_chain_packet as packet_builder
from scripts import collect_telegram_visible_unseen_chain_state as state


def manifest_output(*, row_count: int = 7) -> str:
    rows = [
        {
            "kind": "identity",
            "database": "teleo",
            "current_user": packet_builder.READ_ONLY_DB_ROLE,
            "server_version_num": 160014,
            "server_encoding": "UTF8",
            "database_collation": "C.UTF-8",
            "database_ctype": "C.UTF-8",
            "transaction_read_only": "on",
        }
    ]
    for kind in (
        "schemas",
        "extensions",
        "application_roles",
        "columns",
        "constraints",
        "indexes",
        "sequences",
        "views",
        "functions",
        "triggers",
        "types",
        "policies",
    ):
        rows.append({"kind": kind, "items": []})
    rows.append(
        {
            "kind": "table",
            "schema": "public",
            "table": "claims",
            "row_count": row_count,
            "rowset_md5": "f" * 32,
        }
    )
    return "BEGIN\nSET\n" + "\n".join(json.dumps(item) for item in rows) + "\nROLLBACK\n"


def write_packet(tmp_path: Path) -> Path:
    out = tmp_path / "packet.json"
    packet_builder.write_json(
        out,
        packet_builder.build_packet(
            handler_receipt_path=packet_builder.DEFAULT_HANDLER_RECEIPT.resolve(),
            git_sha="a" * 40,
        ),
    )
    return out


def args_for(packet: Path, tmp_path: Path, *, phase: str = "preflight") -> argparse.Namespace:
    return argparse.Namespace(
        phase=phase,
        packet=packet,
        baseline=None,
        subject_ref=None,
        manifest_sql=Path("ops/postgres_parity_manifest.sql"),
        ssh_target="root@example.invalid",
        ssh_key=tmp_path / "key",
        connect_timeout=1,
        timeout=5,
        fingerprint_timeout=5,
        ssh_attempts=3,
        out=tmp_path / f"{phase}.json",
        markdown_out=tmp_path / f"{phase}.md",
    )


class GoodRunner:
    def __init__(self) -> None:
        self.service_calls = 0

    def __call__(self, command: list[str], input_text: str, _timeout: int) -> state.CommandResult:
        remote_command = command[-1]
        if "git rev-parse HEAD" in remote_command:
            sha = "b" * 40
            return state.CommandResult(
                0,
                f"deploy_head={sha}\nlast_deploy_sha={sha}\ndeploy_stamp_ancestor=true\n",
                "",
            )
        if "systemctl show" in remote_command:
            self.service_calls += 1
            return state.CommandResult(
                0,
                "\n".join(
                    [
                        "ActiveState=active",
                        "SubState=running",
                        "MainPID=123",
                        "NRestarts=0",
                        "ExecMainStartTimestamp=Wed 2026-07-15 00:00:00 UTC",
                    ]
                ),
                "",
            )
        if "'subject_ref'" in input_text:
            assert "default_transaction_read_only=on" in remote_command
            assert "-c transaction_read_only=on" in remote_command
            assert f"role={packet_builder.READ_ONLY_DB_ROLE}" in remote_command
            assert "psql -X" in remote_command
            payload = {
                "subject_ref": "c0000000",
                "matches": [
                    {
                        "table": "public.beliefs",
                        "row": {"id": "c0000000-0000-4000-8000-000000000001"},
                        "evidence_count": None,
                    }
                ],
            }
            return state.CommandResult(0, f"BEGIN\n{json.dumps(payload)}\nROLLBACK\n", "")
        if "postgres_parity_manifest" not in remote_command and input_text.startswith("\\set ON_ERROR_STOP"):
            assert "default_transaction_read_only=on" in remote_command
            assert "-c transaction_read_only=on" in remote_command
            assert f"role={packet_builder.READ_ONLY_DB_ROLE}" in remote_command
            assert "psql -X" in remote_command
            return state.CommandResult(0, manifest_output(), "")
        raise AssertionError(f"unexpected command: {command} / input={input_text[:80]}")


def test_preflight_passes_with_two_identical_read_only_fingerprints(tmp_path: Path) -> None:
    args = args_for(write_packet(tmp_path), tmp_path)

    result = state.collect_state(args, runner=GoodRunner())

    assert result["status"] == "pass"
    assert result["ready_for_action_time_authorization"] is True
    assert result["telegram_visible_messages_sent_by_collector"] is False
    assert result["mutates_db"] is False
    assert all(result["packet_checks"].values())
    assert all(result["remote_checks"].values())
    assert len(result["state_token_sha256"]) == 64


def test_preflight_fails_when_second_fingerprint_drifts(tmp_path: Path) -> None:
    args = args_for(write_packet(tmp_path), tmp_path)
    fingerprint_calls = 0
    good = GoodRunner()

    def drift_runner(command: list[str], input_text: str, timeout: int) -> state.CommandResult:
        nonlocal fingerprint_calls
        if input_text.startswith("\\set ON_ERROR_STOP") and "'subject_ref'" not in input_text:
            fingerprint_calls += 1
            return state.CommandResult(0, manifest_output(row_count=7 + fingerprint_calls - 1), "")
        return good(command, input_text, timeout)

    result = state.collect_state(args, runner=drift_runner)

    assert result["status"] == "fail"
    assert result["remote_checks"]["canonical_fingerprint_unchanged_during_probe"] is False


def test_preflight_accepts_newer_workspace_head_when_deployed_stamp_is_ancestor(tmp_path: Path) -> None:
    args = args_for(write_packet(tmp_path), tmp_path)

    class AheadRunner(GoodRunner):
        def __call__(self, command: list[str], input_text: str, timeout: int) -> state.CommandResult:
            if "git rev-parse HEAD" in command[-1]:
                return state.CommandResult(
                    0,
                    f"deploy_head={'c' * 40}\nlast_deploy_sha={'b' * 40}\ndeploy_stamp_ancestor=true\n",
                    "",
                )
            return super().__call__(command, input_text, timeout)

    result = state.collect_state(args, runner=AheadRunner())

    assert result["status"] == "pass"
    assert result["remote"]["workspace_head_matches_deployed_stamp"] is False
    assert result["remote_checks"]["deployed_stamp_is_ancestor_of_workspace_head"] is True


def test_preflight_retries_transient_ssh_failure(tmp_path: Path) -> None:
    args = args_for(write_packet(tmp_path), tmp_path)
    good = GoodRunner()
    deploy_attempts = 0

    def transient_runner(command: list[str], input_text: str, timeout: int) -> state.CommandResult:
        nonlocal deploy_attempts
        if "git rev-parse HEAD" in command[-1]:
            deploy_attempts += 1
            if deploy_attempts == 1:
                return state.CommandResult(255, "", "Connection timed out during banner exchange")
        return good(command, input_text, timeout)

    result = state.collect_state(args, runner=transient_runner)

    assert result["status"] == "pass"
    assert result["remote"]["readback_attempts"]["deploy"] == 2


def test_preflight_rejects_packet_bound_to_different_tooling_source(tmp_path: Path) -> None:
    packet_path = write_packet(tmp_path)
    packet = json.loads(packet_path.read_text(encoding="utf-8"))
    packet["tooling_binding"]["capture_verifier"]["sha256"] = "0" * 64
    packet_path.write_text(json.dumps(packet), encoding="utf-8")

    result = state.collect_state(args_for(packet_path, tmp_path), runner=GoodRunner())

    assert result["status"] == "fail"
    assert result["packet_checks"]["packet_tooling_hashes_match_current_sources"] is False


def test_postflight_binds_baseline_fingerprint_service_and_subject(tmp_path: Path) -> None:
    packet = write_packet(tmp_path)
    pre_args = args_for(packet, tmp_path)
    preflight = state.collect_state(pre_args, runner=GoodRunner())
    baseline = tmp_path / "preflight.json"
    state.write_json(baseline, preflight)
    post_args = args_for(packet, tmp_path, phase="postflight")
    post_args.baseline = baseline
    post_args.subject_ref = "c0000000"

    postflight = state.collect_state(post_args, runner=GoodRunner())

    assert postflight["status"] == "pass"
    assert postflight["ready_for_visible_capture_verification"] is True
    assert all(postflight["baseline_checks"].values())
    assert postflight["remote_checks"]["subject_resolved_to_exactly_one_db_object"] is True


def test_postflight_rejects_baseline_fingerprint_mismatch(tmp_path: Path) -> None:
    packet = write_packet(tmp_path)
    pre_args = args_for(packet, tmp_path)
    preflight = state.collect_state(pre_args, runner=GoodRunner())
    preflight = copy.deepcopy(preflight)
    preflight["remote"]["fingerprint_after"]["fingerprint_sha256"] = "0" * 64
    baseline = tmp_path / "preflight.json"
    state.write_json(baseline, preflight)
    post_args = args_for(packet, tmp_path, phase="postflight")
    post_args.baseline = baseline
    post_args.subject_ref = "c0000000"

    postflight = state.collect_state(post_args, runner=GoodRunner())

    assert postflight["status"] == "fail"
    assert postflight["baseline_checks"]["canonical_fingerprint_matches_preflight"] is False


def test_subject_sql_rejects_untrusted_input() -> None:
    try:
        state.subject_readback_sql("c0000000'; delete from public.claims; --")
    except ValueError as exc:
        assert "subject_ref" in str(exc)
    else:
        raise AssertionError("unsafe subject ref was accepted")


class NoSshRunner:
    def __init__(self) -> None:
        self.calls = 0

    def __call__(self, _command: list[str], _input_text: str, _timeout: int) -> state.CommandResult:
        self.calls += 1
        raise AssertionError("SSH runner must not be called after local fail-closed rejection")


def test_write_capable_manifest_override_is_rejected_before_ssh(tmp_path: Path) -> None:
    packet = write_packet(tmp_path)
    malicious = tmp_path / "write.sql"
    malicious.write_text(
        "\\set ON_ERROR_STOP on\nbegin;\nupdate public.claims set status = 'live';\ncommit;\n",
        encoding="utf-8",
    )
    args = args_for(packet, tmp_path)
    args.manifest_sql = malicious
    runner = NoSshRunner()

    result = state.collect_state(args, runner=runner)

    assert result["status"] == "fail"
    assert result["packet_checks"]["manifest_override_absent_or_exact_canonical_path"] is False
    assert result["remote"]["readback_attempts"] == {}
    assert runner.calls == 0
    assert state._manifest_sql_is_statically_read_only(malicious.read_text(encoding="utf-8")) is False


def test_manifest_hash_mismatch_is_rejected_before_ssh(tmp_path: Path) -> None:
    packet_path = write_packet(tmp_path)
    packet = json.loads(packet_path.read_text(encoding="utf-8"))
    packet["manifest_binding"]["sha256"] = "0" * 64
    packet_path.write_text(json.dumps(packet), encoding="utf-8")
    runner = NoSshRunner()

    result = state.collect_state(args_for(packet_path, tmp_path), runner=runner)

    assert result["status"] == "fail"
    assert result["packet_checks"]["manifest_packet_sha256_matches_canonical_file"] is False
    assert result["remote"]["readback_attempts"] == {}
    assert runner.calls == 0


def test_cli_removes_arbitrary_manifest_override() -> None:
    with pytest.raises(SystemExit):
        state.parse_args(["--manifest-sql", "write.sql"])


def test_fingerprint_command_enforces_read_only_session_role_and_disables_psql_rc() -> None:
    command = state.fingerprint_readback_command()

    assert "default_transaction_read_only=on" in command
    assert "-c transaction_read_only=on" in command
    assert f"role={packet_builder.READ_ONLY_DB_ROLE}" in command
    assert "psql -X" in command
