import json
from pathlib import Path

import pytest

from ops.capture_vps_canonical_postgres_snapshot import (
    SAFE_AUTHORIZATION_REF_RE,
    SAFE_SSH_TARGET_RE,
    SOURCE_SNAPSHOT_RECEIPT_SCHEMA,
    build_provenance_binding,
    canonical_sha256,
    cleanup_failed_output,
    load_service_state,
    load_snapshot_metadata,
    parse_args,
    remote_capture_script,
    toc_counts,
)


def test_remote_capture_uses_one_exported_read_only_snapshot() -> None:
    script = remote_capture_script(
        run_id="canonical-20260711",
        container="teleo-pg",
        database="teleo",
        service="leoclean-gateway.service",
    )

    assert "begin transaction isolation level repeatable read read only" in script
    assert "select pg_export_snapshot()" in script
    assert "[0-9A-F]+-[0-9A-F]+-[0-9]+" in script
    assert '--snapshot="$snapshot_id"' in script
    assert '-v "snapshot_id=$snapshot_id"' in script
    assert "txid_current_snapshot()::text" in script
    assert "pg_current_wal_lsn()::text" in script
    assert "system_identifier::text from pg_control_system()" in script
    assert "source-snapshot.json" in script
    assert "--no-owner" in script
    assert "--no-acl" not in script
    assert "cmp -s" in script
    assert 'assert_service_healthy "$remote_root/service-before.txt"' in script
    assert 'assert_service_healthy "$remote_root/service-after.txt"' in script
    assert "systemctl restart" not in script
    assert "docker restart" not in script


def test_manifest_hash_order_is_collation_independent() -> None:
    manifest = Path("ops/postgres_parity_manifest.sql").read_text()

    assert 'collate "C"' in manifest
    assert "'server_address', host(inet_server_addr())" in manifest
    assert "'kb_gate_owner', 'kb_apply', 'kb_review'" in manifest
    assert "from pg_db_role_setting database_setting" in manifest
    assert "'database_settings'" in manifest
    assert "'kind', 'role_memberships'" in manifest
    assert "'inherit_option', membership.inherit_option" in manifest
    assert "'set_option', membership.set_option" in manifest
    assert "'kind', 'object_ownership'" in manifest
    assert "'kind', 'object_acl'" in manifest
    assert "aclexplode(attribute.attacl)" in manifest


def test_toc_counts_database_objects(tmp_path: Path) -> None:
    toc = tmp_path / "dump.toc"
    toc.write_text(
        "; header\n"
        "1; 0 0 TABLE DATA public claims postgres\n"
        "2; 0 0 CONSTRAINT public claims claims_pkey postgres\n"
        "3; 0 0 FK CONSTRAINT public evidence evidence_claim_fkey postgres\n"
        "4; 0 0 INDEX public claims claims_text_idx postgres\n"
    )

    assert toc_counts(toc) == {"entries": 4, "table_data": 1, "constraints": 2, "indexes": 1}


def test_failure_cleanup_never_removes_preexisting_output(tmp_path: Path) -> None:
    existing = tmp_path / "existing"
    existing.mkdir()
    marker = existing / "user-file"
    marker.write_text("keep")
    cleanup_failed_output(existing, preexisting=True)

    created_by_run = tmp_path / "created-by-run"
    created_by_run.mkdir()
    cleanup_failed_output(created_by_run, preexisting=False)

    assert marker.read_text() == "keep"
    assert not created_by_run.exists()


def test_source_service_state_requires_healthy_unchanged_capable_fields(tmp_path: Path) -> None:
    state_path = tmp_path / "service.txt"
    state_path.write_text("ActiveState=active\nSubState=running\nMainPID=347406\nNRestarts=0\n")

    assert load_service_state(state_path)["MainPID"] == "347406"

    state_path.write_text("ActiveState=failed\nSubState=dead\nMainPID=0\nNRestarts=0\n")
    with pytest.raises(RuntimeError, match="not active/running"):
        load_service_state(state_path)


def test_ssh_target_rejects_open_ssh_option_injection() -> None:
    assert SAFE_SSH_TARGET_RE.fullmatch("root@77.42.65.182")
    assert SAFE_SSH_TARGET_RE.fullmatch("teleo-staging-1.internal")
    assert not SAFE_SSH_TARGET_RE.fullmatch("-oProxyCommand=malicious")
    assert not SAFE_SSH_TARGET_RE.fullmatch("root@host command")


def test_source_snapshot_metadata_and_provenance_binding(tmp_path: Path) -> None:
    snapshot_path = tmp_path / "source-snapshot.json"
    snapshot_path.write_text(
        json.dumps(
            {
                "exported_snapshot_id": "00000003-000001A2-1",
                "txid_snapshot": "100:120:105,111",
                "wal_lsn": "0/16B6C50",
                "system_identifier": "7649789040005668902",
                "captured_at_utc": "2026-07-15T01:02:03.456789+00:00",
            }
        )
    )

    snapshot = load_snapshot_metadata(snapshot_path)
    binding = build_provenance_binding(
        run_id="canonical-20260715",
        authorization_ref="codex-delegation:019f47af-f2cc-7c10-b928-a9283afa2621",
        source={
            "ssh_target": "root@77.42.65.182",
            "container": "teleo-pg",
            "database": "teleo",
            "service": "leoclean-gateway.service",
        },
        snapshot=snapshot,
        dump_sha256="1" * 64,
        manifest_sql_sha256="2" * 64,
        source_manifest_sha256="3" * 64,
        source_context_sha256="4" * 64,
    )

    assert binding["algorithm"] == "sha256"
    assert binding["payload"] == {
        "receipt_schema": SOURCE_SNAPSHOT_RECEIPT_SCHEMA,
        "run_id": "canonical-20260715",
        "capture_authorization_ref": "codex-delegation:019f47af-f2cc-7c10-b928-a9283afa2621",
        "source": {
            "ssh_target": "root@77.42.65.182",
            "container": "teleo-pg",
            "database": "teleo",
            "service": "leoclean-gateway.service",
        },
        "exported_snapshot_id": "00000003-000001A2-1",
        "txid_snapshot": "100:120:105,111",
        "wal_lsn": "0/16B6C50",
        "source_system_identifier": "7649789040005668902",
        "dump_sha256": "1" * 64,
        "manifest_sql_sha256": "2" * 64,
        "source_manifest_sha256": "3" * 64,
        "source_context_sha256": "4" * 64,
    }
    assert binding["sha256"] == canonical_sha256(binding["payload"])


@pytest.mark.parametrize(
    ("field", "value"),
    [
        ("exported_snapshot_id", "unsafe snapshot"),
        ("txid_snapshot", "100:120:not-a-txid"),
        ("wal_lsn", "not-an-lsn"),
        ("system_identifier", "system-1"),
    ],
)
def test_source_snapshot_metadata_rejects_invalid_identity(tmp_path: Path, field: str, value: str) -> None:
    payload = {
        "exported_snapshot_id": "00000003-000001A2-1",
        "txid_snapshot": "100:120:",
        "wal_lsn": "0/16B6C50",
        "system_identifier": "7649789040005668902",
        "captured_at_utc": "2026-07-15T01:02:03+00:00",
    }
    payload[field] = value
    path = tmp_path / "source-snapshot.json"
    path.write_text(json.dumps(payload))

    with pytest.raises(RuntimeError, match=field):
        load_snapshot_metadata(path)


def test_authorization_ref_is_required_and_safe_metadata(tmp_path: Path) -> None:
    ssh_key = tmp_path / "id_ed25519"
    ssh_key.write_text("test-only")
    manifest = tmp_path / "manifest.sql"
    manifest.write_text("select 1;\n")
    base = [
        "--execute",
        "--ssh-target",
        "root@77.42.65.182",
        "--ssh-key",
        str(ssh_key),
        "--manifest",
        str(manifest),
        "--run-id",
        "canonical-20260715",
        "--output-dir",
        str(tmp_path / "capture"),
    ]

    with pytest.raises(SystemExit):
        parse_args(base)
    with pytest.raises(SystemExit):
        parse_args([*base, "--authorization-ref", "unsafe authorization ref"])

    authorization_ref = "codex-delegation:019f47af-f2cc-7c10-b928-a9283afa2621"
    args = parse_args([*base, "--authorization-ref", authorization_ref])
    assert args.authorization_ref == authorization_ref
    assert SAFE_AUTHORIZATION_REF_RE.fullmatch(authorization_ref)
    assert not SAFE_AUTHORIZATION_REF_RE.fullmatch("authorization\nforged")
