"""Unit tests for scripts/kb_rich_proposal_apply_packet.py."""

from __future__ import annotations

import json
import sys
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))

import kb_rich_proposal_apply_packet as packet  # noqa: E402


def _plan_file(tmp_path: Path, *, blocked: bool = True) -> Path:
    plan = {
        "creation_plans": [
            {
                "proposal_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
                "rollback_rehearsal_sql": "begin;\ninsert into public.claims (id) values ('11111111-1111-1111-1111-111111111111');\nrollback;\n",
                "planned_rows": {
                    "reasoning_tools": [
                        {
                            "id": "44444444-4444-4444-4444-444444444444",
                            "agent_id": "55555555-5555-5555-5555-555555555555",
                            "name": "Generated Tool",
                            "category": "strategy_framework",
                            "description": "generated reasoning tool",
                        }
                    ],
                    "claims": [
                        {
                            "id": "11111111-1111-1111-1111-111111111111",
                            "type": "structural",
                            "confidence": 0.7,
                            "text": "generated claim",
                        }
                    ],
                    "sources": [{"id": "22222222-2222-2222-2222-222222222222", "source_type": "other"}],
                    "claim_edges": [
                        {
                            "from_claim": "11111111-1111-1111-1111-111111111111",
                            "to_claim": "33333333-3333-3333-3333-333333333333",
                            "edge_type": "relates",
                        }
                    ],
                    "claim_evidence": [
                        {
                            "claim_id": "11111111-1111-1111-1111-111111111111",
                            "source_id": "22222222-2222-2222-2222-222222222222",
                            "role": "grounds",
                        }
                    ],
                },
                "blocked_fragments": [{"kind": "edge", "reasons": ["still blocked"]}] if blocked else [],
                "unresolved_design_decisions": [{"decision": "non_claim_target"}] if blocked else [],
            }
        ]
    }
    path = tmp_path / "plan.json"
    path.write_text(json.dumps(plan), encoding="utf-8")
    return path


def test_build_packet_keeps_commit_sql_opt_in(tmp_path: Path):
    result = packet.build_packet(_plan_file(tmp_path), emit_commit_sql=False)
    assert result["row_counts"] == {
        "reasoning_tools": 1,
        "claims": 1,
        "sources": 1,
        "claim_evidence": 1,
        "claim_edges": 1,
    }
    assert result["marks_original_proposals_applied"] is False
    assert result["sql"]["apply_commit"] is None
    assert result["sql"]["apply_rollback_rehearsal"].strip().endswith("rollback;")


def test_build_packet_collects_required_existing_claims(tmp_path: Path):
    result = packet.build_packet(_plan_file(tmp_path), emit_commit_sql=True)
    assert result["sql"]["apply_commit"].strip().endswith("commit;")
    required = result["required_existing_claims"]
    assert required == [{"id": "33333333-3333-3333-3333-333333333333", "reason": "claim_edges.to_claim"}]
    assert "missing_required_existing_claims" in result["sql"]["preflight"]
    assert "generated_reasoning_tool_rows" in result["sql"]["preflight"]
    assert "generated_claim_rows" in result["sql"]["postflight"]
    assert "from public.reasoning_tools" in result["sql"]["postflight"]
    assert "where superseded_by in" in result["sql"]["postflight"]
    assert "delete from public.claim_edges" in result["sql"]["delete_rollback"]
    assert "delete from public.reasoning_tools" in result["sql"]["delete_rollback"]
    assert "set superseded_by = null" in result["sql"]["delete_rollback"]


def test_write_packet_files(tmp_path: Path):
    result = packet.build_packet(_plan_file(tmp_path), emit_commit_sql=True)
    output_dir = tmp_path / "packet"
    packet.write_packet_files(result, output_dir)
    assert (output_dir / "production-preflight.sql").exists()
    assert (output_dir / "production-apply-authorized-commit.sql").exists()
    summary = json.loads((output_dir / "production-apply-packet.json").read_text())
    assert "sql" not in summary
    assert summary["proposal_ids"] == ["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"]


def test_mark_original_proposal_applied_rejects_partial_packet(tmp_path: Path):
    with pytest.raises(ValueError, match="partial packet"):
        packet.build_packet(
            _plan_file(tmp_path, blocked=True),
            emit_commit_sql=True,
            mark_original_proposals_applied=True,
        )


def test_mark_original_proposal_applied_for_full_packet(tmp_path: Path):
    result = packet.build_packet(
        _plan_file(tmp_path, blocked=False),
        emit_commit_sql=True,
        mark_original_proposals_applied=True,
        applied_by_handle="codex-test",
    )

    assert result["marks_original_proposals_applied"] is True
    assert result["applied_by_handle"] == "codex-test"
    assert "status = 'applied'" in result["sql"]["apply_commit"]
    assert "applied_by_handle = 'codex-test'" in result["sql"]["apply_commit"]
    assert "expected 1; original proposals were not all approved" in result["sql"]["apply_commit"]
    assert "rollback;" in result["sql"]["apply_rollback_rehearsal"]
    assert "from kb_stage.kb_proposals" in result["sql"]["postflight"]
    assert "status = 'approved'" in result["sql"]["delete_rollback"]
    assert "original proposals were not all applied" in result["sql"]["delete_rollback"]
