#!/usr/bin/env python3
"""Generate a cross-surface companion packet for mapped rich KB proposals.

The mapped rich proposal packet creates claim/source/evidence/edge rows. Some
approved fragments target non-claim surfaces. This packet handles the one
currently safe cross-surface write: after Claim D exists, move the active Leo
policy anchor from the retired compressed internet-finance claim to Claim D.

Other fragments are retained as explicit deferrals because the current schema
does not provide a safe direct relationship from claims to governance gates,
concept maps, or Rio strategy context.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Any

HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))

from kb_rich_proposal_creation_plan import _sql_literal  # noqa: E402

POLICY_NODE_ID = "52bc4264-330e-489d-920c-a493fd2d985a"
POLICY_NODE_TITLE = "Multi-agent public intelligence"
POLICY_TELOS_ANCHOR_ID = "c7dd984c-ac1b-43f9-9996-576ddf61c6d2"
TELOS_SHARED_ROOT_ID = "13100000-0000-0000-0000-000000000001"

OLD_INTERNET_FINANCE_CLAIM_ID = "d0000000-0000-0000-0000-000000000005"
CLAIM_D_ID = "e6ce4e36-15d2-53bc-8b74-dda0efc095c1"
APPROVAL_SOURCE_ID = "9a0d2429-b98b-585a-980c-0176db7f9fe7"
ANCHOR_ID = "47f128a0-36c5-4b60-97d4-48c32fa48bc7"

QUALITY_GATE_ID = "fa88e31b-6fca-4801-bf72-f5db81950a11"
EVIDENCE_BAR_ID = "45695897-2a9c-4279-ac25-e59f166dec6a"

ANCHOR_NOTE = (
    "Mapped rich proposal 14fa5ecc applied: active Leo policy now anchors to Claim D "
    "(internet finance is a candidate mechanism, not the telos) instead of the retired "
    "compressed internet-finance claim."
)
OLD_ANCHOR_NOTE = (
    "Telos/policy grounding approved by m3ta: capital allocation expresses priorities "
    "and must be coordinated with collective intelligence."
)


def _uuid_values(values: list[str], *, column_name: str = "id") -> str:
    rows = ",\n    ".join(f"('{value}'::uuid)" for value in values)
    return f"(values\n    {rows}\n  ) as v({column_name})"


def build_preflight_sql() -> str:
    return f"""\\set ON_ERROR_STOP on
-- Cross-surface companion preflight. Read-only.
-- Run after the mapped rich proposal claim/source packet has been applied.

select 'policy_anchor_current' as check_name,
       id::text,
       from_node_id::text,
       anchor_role,
       claim_id::text,
       source_id::text,
       left(note, 220) as note
from public.strategy_node_anchors
where id = { _sql_literal(ANCHOR_ID) }::uuid;

select 'base_claim_d_exists' as check_name,
       id::text,
       status,
       left(text, 220) as text
from public.claims
where id = { _sql_literal(CLAIM_D_ID) }::uuid;

select 'approval_source_exists' as check_name,
       id::text,
       source_type,
       left(coalesce(excerpt, ''), 220) as excerpt
from public.sources
where id = { _sql_literal(APPROVAL_SOURCE_ID) }::uuid;

select 'telos_anchor_already_exists' as check_name,
       id::text,
       from_node_id::text,
       anchor_role,
       to_shared_root_id::text,
       left(note, 220) as note
from public.strategy_node_anchors
where id = { _sql_literal(POLICY_TELOS_ANCHOR_ID) }::uuid;

select 'governance_gate_known' as check_name,
       id::text,
       name,
       left(criteria, 180) as criteria,
       left(evidence_bar, 180) as evidence_bar
from public.governance_gates
where id in (select id from {_uuid_values([QUALITY_GATE_ID, EVIDENCE_BAR_ID])})
order by name;
"""


def build_apply_sql(*, commit: bool) -> str:
    terminator = "commit;" if commit else "rollback;"
    return f"""\\set ON_ERROR_STOP on
begin;

do $$
declare
  updated int;
begin
  if not exists (select 1 from public.claims where id = { _sql_literal(CLAIM_D_ID) }::uuid) then
    raise exception 'cross-surface packet requires Claim D % to exist before anchor update', { _sql_literal(CLAIM_D_ID) };
  end if;

  if not exists (select 1 from public.sources where id = { _sql_literal(APPROVAL_SOURCE_ID) }::uuid) then
    raise exception 'cross-surface packet requires approval source % to exist before anchor update', { _sql_literal(APPROVAL_SOURCE_ID) };
  end if;

  update public.strategy_node_anchors
     set claim_id = { _sql_literal(CLAIM_D_ID) }::uuid,
         weight = 0.85,
         note = { _sql_literal(ANCHOR_NOTE) }
   where id = { _sql_literal(ANCHOR_ID) }::uuid
     and from_node_id = { _sql_literal(POLICY_NODE_ID) }::uuid
     and anchor_role = 'justified_by'
     and claim_id = { _sql_literal(OLD_INTERNET_FINANCE_CLAIM_ID) }::uuid;

  get diagnostics updated = row_count;
  if updated <> 1 then
    raise exception 'cross-surface anchor update affected % row(s), expected 1; anchor was not at old claim state', updated;
  end if;
end $$;

{terminator}
"""


def build_postflight_sql() -> str:
    return f"""\\set ON_ERROR_STOP on
-- Cross-surface postflight row-level readback.

select id::text,
       from_node_id::text,
       anchor_role,
       claim_id::text,
       source_id::text,
       weight,
       left(note, 240) as note
from public.strategy_node_anchors
where id = { _sql_literal(ANCHOR_ID) }::uuid;

select c.id::text,
       c.status,
       c.superseded_by::text,
       left(c.text, 240) as text
from public.claims c
where c.id in (select id from {_uuid_values([OLD_INTERNET_FINANCE_CLAIM_ID, CLAIM_D_ID])})
order by c.id;

select id::text,
       anchor_role,
       to_shared_root_id::text,
       left(note, 240) as note
from public.strategy_node_anchors
where id = { _sql_literal(POLICY_TELOS_ANCHOR_ID) }::uuid;
"""


def build_delete_rollback_sql() -> str:
    return f"""\\set ON_ERROR_STOP on
begin;

do $$
declare
  restored int;
begin
  update public.strategy_node_anchors
     set claim_id = { _sql_literal(OLD_INTERNET_FINANCE_CLAIM_ID) }::uuid,
         source_id = null,
         weight = 0.7,
         note = { _sql_literal(OLD_ANCHOR_NOTE) }
   where id = { _sql_literal(ANCHOR_ID) }::uuid
     and from_node_id = { _sql_literal(POLICY_NODE_ID) }::uuid
     and anchor_role = 'justified_by'
     and claim_id = { _sql_literal(CLAIM_D_ID) }::uuid;

  get diagnostics restored = row_count;
  if restored <> 1 then
    raise exception 'cross-surface anchor rollback affected % row(s), expected 1; anchor was not at Claim D state', restored;
  end if;
end $$;

commit;
"""


def build_packet() -> dict[str, Any]:
    return {
        "packet_version": 1,
        "purpose": "Companion packet for mapped rich proposal non-claim surfaces.",
        "production_apply_status": "not_executed",
        "base_packet_required_first": "production-apply-packet-mapped-20260709",
        "safe_writes": [
            {
                "table": "public.strategy_node_anchors",
                "operation": "update",
                "id": ANCHOR_ID,
                "from_node_id": POLICY_NODE_ID,
                "from_node_title": POLICY_NODE_TITLE,
                "old_claim_id": OLD_INTERNET_FINANCE_CLAIM_ID,
                "new_claim_id": CLAIM_D_ID,
                "approval_source_id": APPROVAL_SOURCE_ID,
                "reason": "Replace the active Leo policy anchor that points at the retiring compressed internet-finance claim with the approved Claim D boundary claim.",
            }
        ],
        "already_represented": [
            {
                "target": "teleo telos",
                "table": "public.strategy_node_anchors",
                "id": POLICY_TELOS_ANCHOR_ID,
                "to_shared_root_id": TELOS_SHARED_ROOT_ID,
                "reason": "The active Leo policy already serves the Teleo telos shared root; the Claim D anchor update keeps internet finance subordinate to that telos.",
            }
        ],
        "known_non_writable_targets": [
            {
                "target": "governance_gate:quality-gate",
                "existing_row_id": QUALITY_GATE_ID,
                "reason": "Governance gate exists, but current schema has no claim-to-governance-gate edge/anchor table.",
            },
            {
                "target": "governance_gate:evidence-bar",
                "existing_row_id": EVIDENCE_BAR_ID,
                "reason": "Governance gate exists, but current schema has no claim-to-governance-gate edge/anchor table.",
            },
            {
                "target": "concept_map:distributed_market_intelligence",
                "existing_row_id": None,
                "reason": "No first-class concept-map table exists in the current live schema.",
            },
            {
                "target": "rio:capital-deployment-layer",
                "existing_row_id": None,
                "reason": "No exact Rio strategy node or claim target exists in the current live schema.",
            },
            {
                "target": "rio:active strategy v1 / internet-finance capital product",
                "existing_row_id": None,
                "reason": "No exact Rio active strategy node exists in the current live schema.",
            },
        ],
        "sql": {
            "preflight": build_preflight_sql(),
            "apply_rollback_rehearsal": build_apply_sql(commit=False),
            "apply_commit": build_apply_sql(commit=True),
            "postflight": build_postflight_sql(),
            "delete_rollback": build_delete_rollback_sql(),
        },
    }


def write_packet_files(packet: dict[str, Any], output_dir: Path) -> None:
    output_dir.mkdir(parents=True, exist_ok=True)
    sql = packet["sql"]
    (output_dir / "cross-surface-preflight.sql").write_text(sql["preflight"], encoding="utf-8")
    (output_dir / "cross-surface-apply-rollback-rehearsal.sql").write_text(
        sql["apply_rollback_rehearsal"], encoding="utf-8"
    )
    (output_dir / "cross-surface-apply-authorized-commit.sql").write_text(sql["apply_commit"], encoding="utf-8")
    (output_dir / "cross-surface-postflight.sql").write_text(sql["postflight"], encoding="utf-8")
    (output_dir / "cross-surface-delete-rollback.sql").write_text(sql["delete_rollback"], encoding="utf-8")
    summary = {key: value for key, value in packet.items() if key != "sql"}
    (output_dir / "cross-surface-packet.json").write_text(
        json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8"
    )


def parse_args(argv: list[str]) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output-dir", type=Path, help="directory to write packet files")
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    args = parse_args(sys.argv[1:] if argv is None else argv)
    packet = build_packet()
    if args.output_dir:
        write_packet_files(packet, args.output_dir)
    print(json.dumps({key: value for key, value in packet.items() if key != "sql"}, indent=2, sort_keys=True))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
