#!/usr/bin/env python3
"""Generate a Rio strategy-context companion packet for mapped KB proposals.

The mapped rich proposal packet creates the canonical claim/source/evidence and
claim-edge rows. Some remaining approved fragments target Rio strategy context,
not claim graph rows. This packet handles that surface by creating two Rio
strategy nodes and exact anchors after the mapped base packet has created the
required successor claims.

Governance-gate and concept-map fragments remain explicit deferrals because the
current live schema has no safe direct edge table for those targets.
"""

from __future__ import annotations

import argparse
import json
import sys
import uuid
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

PROPOSAL_ID = "14fa5ecc-ac7a-41c1-807d-a2e85b936617"
BASE_PACKET_REQUIRED_FIRST = "production-apply-packet-mapped-20260709"

RIO_AGENT_ID = "22222222-2222-2222-2222-222222222222"
BACK_SHARED_ROOT_ID = "6248818e-fe16-4d3b-8008-ef9752b80632"

CLAIM_A_ID = "004195cc-ab62-526c-90fb-51cb0f826ec0"
CLAIM_C_ID = "67003e10-e1ee-5905-8b51-1b0cb263ec11"
CLAIM_D_ID = "e6ce4e36-15d2-53bc-8b74-dda0efc095c1"

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

ID_NAMESPACE = uuid.uuid5(uuid.NAMESPACE_URL, "teleo:working-leo:rio-strategy-context")


def stable_id(key: str) -> str:
    return str(uuid.uuid5(ID_NAMESPACE, key))


CAPITAL_LAYER_NODE_ID = stable_id("strategy-node:rio-capital-deployment-layer")
INTERNET_FINANCE_PRODUCT_NODE_ID = stable_id("strategy-node:rio-internet-finance-capital-product")

CAPITAL_JUSTIFIED_BY_CLAIM_A_ANCHOR_ID = stable_id("anchor:capital-layer:justified-by:claim-a")
CAPITAL_SERVES_BACK_ANCHOR_ID = stable_id("anchor:capital-layer:serves:back-shared-root")
PRODUCT_JUSTIFIED_BY_CLAIM_C_ANCHOR_ID = stable_id("anchor:internet-finance-product:justified-by:claim-c")
PRODUCT_JUSTIFIED_BY_CLAIM_D_ANCHOR_ID = stable_id("anchor:internet-finance-product:justified-by:claim-d")
PRODUCT_SERVES_CAPITAL_LAYER_ANCHOR_ID = stable_id("anchor:internet-finance-product:serves:capital-layer")


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 _jsonb(value: Any) -> str:
    return _sql_literal(json.dumps(value, sort_keys=True)) + "::jsonb"


def strategy_nodes() -> list[dict[str, Any]]:
    return [
        {
            "id": CAPITAL_LAYER_NODE_ID,
            "agent_id": RIO_AGENT_ID,
            "node_type": "policy",
            "title": "Rio capital deployment layer",
            "body": (
                "Rio treats capital allocation as a civilizational steering layer: resources should back "
                "builders through review-gated, market-aware mechanisms that serve Teleo's Back function."
            ),
            "rank": 1,
            "status": "active",
            "horizon": "current strategy context",
            "measure": (
                "Capital-deployment analysis can name the claim evidence, review boundary, and Back shared "
                "root it serves before recommending resource allocation."
            ),
            "source_ref": "mapped-rich-proposal-14fa5ecc:rio-capital-deployment-layer",
            "metadata": {
                "base_packet_required_first": BASE_PACKET_REQUIRED_FIRST,
                "proposal_id": PROPOSAL_ID,
                "source_fragments": [
                    "capital_allocation_is_civilizational_steering supports rio:capital-deployment-layer",
                    "capital_allocation_is_civilizational_steering relates teleo telos / fund-build paths toward better attractors",
                ],
            },
        },
        {
            "id": INTERNET_FINANCE_PRODUCT_NODE_ID,
            "agent_id": RIO_AGENT_ID,
            "node_type": "objective",
            "title": "Rio internet-finance capital product",
            "body": (
                "Rio may evaluate internet-finance mechanisms as capital-product candidates, but only as "
                "instrumental mechanisms. The product must stay subordinate to Teleo's capital-allocation "
                "telos and cannot become the telos itself."
            ),
            "rank": 2,
            "status": "active",
            "horizon": "current strategy context",
            "measure": (
                "Any internet-finance product thesis states its structural advantages, its mechanism-not-telos "
                "boundary, and how it serves the capital deployment layer."
            ),
            "source_ref": "mapped-rich-proposal-14fa5ecc:rio-active-strategy-v1-internet-finance-capital-product",
            "metadata": {
                "base_packet_required_first": BASE_PACKET_REQUIRED_FIRST,
                "proposal_id": PROPOSAL_ID,
                "source_fragments": [
                    "internet_finance_structural_advantages_capital_allocation relates rio:active strategy v1 / internet-finance capital product",
                    "internet_finance_is_mechanism_not_telos constrains rio:active strategy v1 / internet-finance capital product",
                ],
            },
        },
    ]


def strategy_anchors() -> list[dict[str, Any]]:
    return [
        {
            "id": CAPITAL_JUSTIFIED_BY_CLAIM_A_ANCHOR_ID,
            "from_node_id": CAPITAL_LAYER_NODE_ID,
            "anchor_role": "justified_by",
            "claim_id": CLAIM_A_ID,
            "weight": 0.85,
            "note": (
                "Mapped proposal 14fa5ecc: Claim A justifies the Rio capital deployment layer because "
                "capital allocation steers which futures receive resources."
            ),
        },
        {
            "id": CAPITAL_SERVES_BACK_ANCHOR_ID,
            "from_node_id": CAPITAL_LAYER_NODE_ID,
            "anchor_role": "serves",
            "to_shared_root_id": BACK_SHARED_ROOT_ID,
            "weight": 0.80,
            "note": (
                "Maps the fund-build / better-attractors fragment to the existing Back shared root instead "
                "of forcing it into public.claim_edges."
            ),
        },
        {
            "id": PRODUCT_JUSTIFIED_BY_CLAIM_C_ANCHOR_ID,
            "from_node_id": INTERNET_FINANCE_PRODUCT_NODE_ID,
            "anchor_role": "justified_by",
            "claim_id": CLAIM_C_ID,
            "weight": 0.70,
            "note": (
                "Mapped proposal 14fa5ecc: the internet-finance capital product is justified by the "
                "structural-advantages claim, not by the old compressed claim."
            ),
        },
        {
            "id": PRODUCT_JUSTIFIED_BY_CLAIM_D_ANCHOR_ID,
            "from_node_id": INTERNET_FINANCE_PRODUCT_NODE_ID,
            "anchor_role": "justified_by",
            "claim_id": CLAIM_D_ID,
            "weight": 0.85,
            "note": (
                "Represents the proposal's constrains fragment within the current anchor-role enum: "
                "internet finance is a candidate mechanism, not Teleo's telos."
            ),
        },
        {
            "id": PRODUCT_SERVES_CAPITAL_LAYER_ANCHOR_ID,
            "from_node_id": INTERNET_FINANCE_PRODUCT_NODE_ID,
            "anchor_role": "serves",
            "to_node_id": CAPITAL_LAYER_NODE_ID,
            "weight": 0.75,
            "note": (
                "The internet-finance product objective serves Rio's capital deployment layer rather than "
                "standing as a standalone telos."
            ),
        },
    ]


def _node_insert_values() -> str:
    rows = []
    for row in strategy_nodes():
        rows.append(
            "("
            f"{_sql_literal(row['id'])}::uuid, "
            f"{_sql_literal(row['agent_id'])}::uuid, "
            f"{_sql_literal(row['node_type'])}, "
            f"{_sql_literal(row['title'])}, "
            f"{_sql_literal(row['body'])}, "
            f"{row['rank']}, "
            f"{_sql_literal(row['status'])}, "
            f"{_sql_literal(row['horizon'])}, "
            f"{_sql_literal(row['measure'])}, "
            f"{_sql_literal(row['source_ref'])}, "
            f"{_jsonb(row['metadata'])}"
            ")"
        )
    return ",\n    ".join(rows)


def _anchor_insert_values() -> str:
    rows = []
    for row in strategy_anchors():
        rows.append(
            "("
            f"{_sql_literal(row['id'])}::uuid, "
            f"{_sql_literal(row['from_node_id'])}::uuid, "
            f"{_sql_literal(row['anchor_role'])}, "
            f"{_sql_literal(row.get('to_node_id'))}::uuid, "
            f"{_sql_literal(row.get('to_shared_root_id'))}::uuid, "
            f"{_sql_literal(row.get('belief_id'))}::uuid, "
            f"{_sql_literal(row.get('claim_id'))}::uuid, "
            f"{_sql_literal(row.get('source_id'))}::uuid, "
            f"{row['weight']}, "
            f"{_sql_literal(row['note'])}"
            ")"
        )
    return ",\n    ".join(rows)


def build_generated_counts_sql() -> str:
    node_ids = [row["id"] for row in strategy_nodes()]
    anchor_ids = [row["id"] for row in strategy_anchors()]
    return f"""select
  (select count(*) from public.strategy_nodes where id in (select id from {_uuid_values(node_ids)})) as generated_strategy_node_rows,
  (select count(*) from public.strategy_node_anchors where id in (select id from {_uuid_values(anchor_ids)})) as generated_strategy_anchor_rows;
"""


def build_preflight_sql() -> str:
    required_claim_ids = [CLAIM_A_ID, CLAIM_C_ID, CLAIM_D_ID]
    node_titles = ",\n    ".join(_sql_literal(row["title"]) for row in strategy_nodes())
    return f"""\\set ON_ERROR_STOP on
-- Rio strategy-context companion preflight. Read-only.
-- Run after the mapped rich proposal packet has been applied.

select 'rio_agent' as check_name, id::text, handle, kind
from public.agents
where id = {_sql_literal(RIO_AGENT_ID)}::uuid;

select 'required_base_claim' as check_name,
       c.id::text,
       c.status,
       left(c.text, 220) as text
from public.claims c
where c.id in (select id from {_uuid_values(required_claim_ids)})
order by c.id;

select 'back_shared_root' as check_name,
       id::text,
       title,
       left(body, 220) as body
from public.shared_root
where id = {_sql_literal(BACK_SHARED_ROOT_ID)}::uuid;

select 'existing_exact_rio_strategy_title' as check_name,
       id::text,
       agent_id::text,
       title,
       status
from public.strategy_nodes
where agent_id = {_sql_literal(RIO_AGENT_ID)}::uuid
  and title in (
    {node_titles}
  )
order by title;

-- Must be 0/0 before authorized apply.
{build_generated_counts_sql()}

select 'governance_gate_known_not_written' 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;"
    required_claim_ids = [CLAIM_A_ID, CLAIM_C_ID, CLAIM_D_ID]
    node_ids = [row["id"] for row in strategy_nodes()]
    anchor_ids = [row["id"] for row in strategy_anchors()]
    node_titles = ",\n      ".join(_sql_literal(row["title"]) for row in strategy_nodes())
    return f"""\\set ON_ERROR_STOP on
begin;

do $$
declare
  inserted_nodes int;
  inserted_anchors int;
begin
  if not exists (
    select 1 from public.agents where id = {_sql_literal(RIO_AGENT_ID)}::uuid and handle = 'rio'
  ) then
    raise exception 'Rio strategy packet requires rio agent % to exist', {_sql_literal(RIO_AGENT_ID)};
  end if;

  if (
    select count(*) from public.claims where id in (select id from {_uuid_values(required_claim_ids)})
  ) <> {len(required_claim_ids)} then
    raise exception 'Rio strategy packet requires mapped base claims A/C/D to exist before strategy context insert';
  end if;

  if not exists (select 1 from public.shared_root where id = {_sql_literal(BACK_SHARED_ROOT_ID)}::uuid) then
    raise exception 'Rio strategy packet requires Back shared root % to exist', {_sql_literal(BACK_SHARED_ROOT_ID)};
  end if;

  if exists (select 1 from public.strategy_nodes where id in (select id from {_uuid_values(node_ids)})) then
    raise exception 'Rio strategy packet generated strategy node id already exists';
  end if;

  if exists (select 1 from public.strategy_node_anchors where id in (select id from {_uuid_values(anchor_ids)})) then
    raise exception 'Rio strategy packet generated strategy anchor id already exists';
  end if;

  if exists (
    select 1
    from public.strategy_nodes
    where agent_id = {_sql_literal(RIO_AGENT_ID)}::uuid
      and title in (
      {node_titles}
      )
  ) then
    raise exception 'Rio strategy packet refuses to create duplicate exact Rio strategy title';
  end if;

  insert into public.strategy_nodes
    (id, agent_id, node_type, title, body, rank, status, horizon, measure, source_ref, metadata)
  values
    {_node_insert_values()};

  get diagnostics inserted_nodes = row_count;
  if inserted_nodes <> {len(node_ids)} then
    raise exception 'Rio strategy packet inserted % strategy node row(s), expected {len(node_ids)}', inserted_nodes;
  end if;

  insert into public.strategy_node_anchors
    (id, from_node_id, anchor_role, to_node_id, to_shared_root_id, belief_id, claim_id, source_id, weight, note)
  values
    {_anchor_insert_values()};

  get diagnostics inserted_anchors = row_count;
  if inserted_anchors <> {len(anchor_ids)} then
    raise exception 'Rio strategy packet inserted % strategy anchor row(s), expected {len(anchor_ids)}', inserted_anchors;
  end if;
end $$;

{terminator}
"""


def build_postflight_sql() -> str:
    node_ids = [row["id"] for row in strategy_nodes()]
    anchor_ids = [row["id"] for row in strategy_anchors()]
    return f"""\\set ON_ERROR_STOP on
-- Rio strategy-context companion postflight row-level readback.

{build_generated_counts_sql()}

select id::text,
       agent_id::text,
       node_type,
       title,
       rank,
       status,
       source_ref,
       metadata::text
from public.strategy_nodes
where id in (select id from {_uuid_values(node_ids)})
order by rank, title;

select id::text,
       from_node_id::text,
       anchor_role,
       to_node_id::text,
       to_shared_root_id::text,
       claim_id::text,
       weight,
       left(note, 260) as note
from public.strategy_node_anchors
where id in (select id from {_uuid_values(anchor_ids)})
order by from_node_id, anchor_role, id;

select id::text, status, left(text, 220) as text
from public.claims
where id in (select id from {_uuid_values([CLAIM_A_ID, CLAIM_C_ID, CLAIM_D_ID])})
order by id;

select id::text, title, left(body, 220) as body
from public.shared_root
where id = {_sql_literal(BACK_SHARED_ROOT_ID)}::uuid;
"""


def build_delete_rollback_sql() -> str:
    node_ids = [row["id"] for row in strategy_nodes()]
    anchor_ids = [row["id"] for row in strategy_anchors()]
    return f"""\\set ON_ERROR_STOP on
-- Emergency rollback for Rio strategy-context generated rows only.
-- Run only before later production data depends on these generated rows.
begin;

do $$
declare
  deleted_anchors int;
  deleted_nodes int;
begin
  delete from public.strategy_node_anchors
  where id in (select id from {_uuid_values(anchor_ids)});

  get diagnostics deleted_anchors = row_count;
  if deleted_anchors <> {len(anchor_ids)} then
    raise exception 'Rio strategy rollback deleted % strategy anchor row(s), expected {len(anchor_ids)}', deleted_anchors;
  end if;

  delete from public.strategy_nodes
  where id in (select id from {_uuid_values(node_ids)});

  get diagnostics deleted_nodes = row_count;
  if deleted_nodes <> {len(node_ids)} then
    raise exception 'Rio strategy rollback deleted % strategy node row(s), expected {len(node_ids)}', deleted_nodes;
  end if;
end $$;

commit;
"""


def build_packet() -> dict[str, Any]:
    return {
        "packet_version": 1,
        "purpose": "Companion packet for mapped rich proposal Rio strategy context.",
        "production_apply_status": "not_executed",
        "base_packet_required_first": BASE_PACKET_REQUIRED_FIRST,
        "source_proposal_id": PROPOSAL_ID,
        "generated_counts": {
            "strategy_nodes": len(strategy_nodes()),
            "strategy_node_anchors": len(strategy_anchors()),
        },
        "safe_writes": [
            {
                "table": "public.strategy_nodes",
                "operation": "insert",
                "rows": strategy_nodes(),
            },
            {
                "table": "public.strategy_node_anchors",
                "operation": "insert",
                "rows": strategy_anchors(),
            },
        ],
        "required_existing_rows": {
            "public.agents": [RIO_AGENT_ID],
            "public.claims": [CLAIM_A_ID, CLAIM_C_ID, CLAIM_D_ID],
            "public.shared_root": [BACK_SHARED_ROOT_ID],
        },
        "represented_fragments": [
            "capital_allocation_is_civilizational_steering supports rio:capital-deployment-layer",
            "capital_allocation_is_civilizational_steering relates teleo telos / fund-build paths toward better attractors",
            "internet_finance_structural_advantages_capital_allocation relates rio:active strategy v1 / internet-finance capital product",
            "internet_finance_is_mechanism_not_telos constrains rio:active strategy v1 / internet-finance capital product",
            "internet_finance_is_mechanism_not_telos serves teleo telos through product -> capital layer -> Back shared root",
        ],
        "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.",
            },
        ],
        "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 / "rio-strategy-preflight.sql").write_text(sql["preflight"], encoding="utf-8")
    (output_dir / "rio-strategy-apply-rollback-rehearsal.sql").write_text(
        sql["apply_rollback_rehearsal"], encoding="utf-8"
    )
    (output_dir / "rio-strategy-apply-authorized-commit.sql").write_text(sql["apply_commit"], encoding="utf-8")
    (output_dir / "rio-strategy-postflight.sql").write_text(sql["postflight"], encoding="utf-8")
    (output_dir / "rio-strategy-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 / "rio-strategy-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())
