#!/usr/bin/env python3
"""sync_missing_agent_skills.py — One-shot fix for Gap #28.

Adds the missing paperclipSkillSync to הגהת מסמכים and מנתח משפטי
in both companies (1xxx CMP, 8xxx CMPA). Idempotent: safe to re-run.

Design: "אל-כשל" — backup, dry-run mode, idempotent, clear errors.

Usage:
    python sync_missing_agent_skills.py --dry-run   # show plan only
    python sync_missing_agent_skills.py --apply     # actually do it
    python sync_missing_agent_skills.py --verify    # check current state
"""
from __future__ import annotations

import argparse
import asyncio
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import asyncpg
import httpx

PAPERCLIP_DB_URL = os.environ.get(
    "PAPERCLIP_DB_URL", "postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip"
)
PAPERCLIP_API_URL = os.environ.get("PAPERCLIP_API_URL", "http://localhost:3100")
PAPERCLIP_BOARD_API_KEY = os.environ.get("PAPERCLIP_BOARD_API_KEY", "")

BACKUP_DIR = Path("/home/chaim/.paperclip/instances/default/data/backups/manual")

PAPERCLIP_BASE_SKILLS = [
    "paperclipai/paperclip/paperclip",
    "paperclipai/paperclip/paperclip-create-agent",
    "paperclipai/paperclip/paperclip-create-plugin",
    "paperclipai/paperclip/para-memory-files",
]

CMP_COMPANY_ID = "42a7acd0-30c5-4cbd-ac97-7424f65df294"   # 1xxx — רישוי ובניה
CMPA_COMPANY_ID = "8639e837-4c9d-47fa-a76b-95788d651896"  # 8xxx — היטלי השבחה

# Per-agent + per-company desired skills
PLAN: dict[tuple[str, str], list[str]] = {
    # (agent_name, company_id) -> desired skills
    ("מנתח משפטי", CMP_COMPANY_ID):  PAPERCLIP_BASE_SKILLS + ["local/eba6210d5a/legal-decision"],
    ("מנתח משפטי", CMPA_COMPANY_ID): PAPERCLIP_BASE_SKILLS,  # CMPA has no local skills
    ("הגהת מסמכים", CMP_COMPANY_ID):  PAPERCLIP_BASE_SKILLS,
    ("הגהת מסמכים", CMPA_COMPANY_ID): PAPERCLIP_BASE_SKILLS,
}


def fail(msg: str) -> None:
    print(f"❌ {msg}", file=sys.stderr)
    sys.exit(1)


async def fetch_targets() -> list[dict[str, Any]]:
    """Return rows for the agents we plan to update."""
    conn = await asyncpg.connect(PAPERCLIP_DB_URL)
    try:
        rows = await conn.fetch(
            """
            SELECT a.id, a.name, a.company_id::text as company_id,
                   COALESCE(
                       jsonb_array_length(a.adapter_config->'paperclipSkillSync'->'desiredSkills'),
                       0
                   ) as current_skill_count
            FROM agents a
            WHERE a.name IN ('מנתח משפטי', 'הגהת מסמכים')
            ORDER BY a.name, a.company_id
            """
        )
    finally:
        await conn.close()
    return [dict(r) for r in rows]


def backup_agents_table() -> Path:
    BACKUP_DIR.mkdir(parents=True, exist_ok=True)
    stamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
    out = BACKUP_DIR / f"agents-pre-skill-sync-{stamp}.sql"
    env = {**os.environ, "PGPASSWORD": "paperclip"}
    subprocess.run(
        ["pg_dump", "-h", "127.0.0.1", "-p", "54329", "-U", "paperclip",
         "-d", "paperclip", "-t", "agents", "--data-only", "-f", str(out)],
        check=True, env=env,
    )
    return out


async def call_skill_sync(agent_id: str, desired_skills: list[str]) -> tuple[int, dict[str, Any]]:
    """Call POST /api/agents/{id}/skills/sync with the desired skills list."""
    if not PAPERCLIP_BOARD_API_KEY:
        fail("PAPERCLIP_BOARD_API_KEY not set — needed for /api/agents/.../skills/sync")
    url = f"{PAPERCLIP_API_URL}/api/agents/{agent_id}/skills/sync"
    headers = {
        "Authorization": f"Bearer {PAPERCLIP_BOARD_API_KEY}",
        "X-Paperclip-Run-Id": "",
        "Content-Type": "application/json",
    }
    body = {"desiredSkills": desired_skills}
    async with httpx.AsyncClient(timeout=30) as client:
        resp = await client.post(url, headers=headers, json=body)
    try:
        data = resp.json()
    except Exception:
        data = {"raw": resp.text[:500]}
    return resp.status_code, data


async def main() -> None:
    p = argparse.ArgumentParser()
    g = p.add_mutually_exclusive_group(required=True)
    g.add_argument("--dry-run", action="store_true", help="Show plan, do not apply")
    g.add_argument("--apply", action="store_true", help="Actually call the skill-sync API")
    g.add_argument("--verify", action="store_true", help="Show current state only")
    args = p.parse_args()

    targets = await fetch_targets()
    if len(targets) != 4:
        fail(f"Expected 4 target rows (2 agents × 2 companies), got {len(targets)}")

    # Build a map for plan
    by_key = {(r["name"], r["company_id"]): r for r in targets}

    print(f"\n=== Targets in DB ({len(targets)} rows) ===")
    for r in targets:
        company_label = "1xxx CMP" if r["company_id"] == CMP_COMPANY_ID else "8xxx CMPA"
        print(f"  {r['name']:14s} | {company_label} | id={r['id']} | currently {r['current_skill_count']} skills")

    print(f"\n=== Plan ===")
    for (agent_name, company_id), desired in PLAN.items():
        company_label = "1xxx CMP" if company_id == CMP_COMPANY_ID else "8xxx CMPA"
        target = by_key.get((agent_name, company_id))
        if not target:
            print(f"  ❌ {agent_name} in {company_label}: NOT FOUND in DB")
            continue
        print(f"  {agent_name:14s} | {company_label} | will set {len(desired)} skills:")
        for s in desired:
            print(f"      - {s}")

    if args.verify:
        print("\n(verify mode — exiting without changes)")
        return
    if args.dry_run:
        print("\n(dry-run mode — exiting without changes)\nRe-run with --apply to execute.")
        return

    # APPLY mode
    print(f"\n=== Backup ===")
    backup_path = backup_agents_table()
    print(f"  ✓ Backed up agents table → {backup_path}")

    print(f"\n=== Applying skill-sync via API ===")
    failures = []
    for (agent_name, company_id), desired in PLAN.items():
        target = by_key.get((agent_name, company_id))
        if not target:
            failures.append(f"{agent_name} in {company_id}: not found")
            continue
        status, data = await call_skill_sync(target["id"], desired)
        if status >= 400:
            failures.append(f"{agent_name} ({company_id[:8]}...): HTTP {status} — {json.dumps(data)[:200]}")
            print(f"  ❌ {agent_name} ({target['id']}): HTTP {status}")
        else:
            new_count = len(data.get("desiredSkills") or data.get("skills") or [])
            print(f"  ✓ {agent_name} ({target['id']}): HTTP {status} (now {new_count or len(desired)} skills)")

    if failures:
        print(f"\n=== ⚠️ {len(failures)} failures ===")
        for f in failures:
            print(f"  - {f}")
        print(f"\nRollback: psql ... -f {backup_path}")
        sys.exit(1)

    # Verify
    print(f"\n=== Post-apply verification ===")
    final = await fetch_targets()
    for r in final:
        company_label = "1xxx CMP" if r["company_id"] == CMP_COMPANY_ID else "8xxx CMPA"
        emoji = "✓" if r["current_skill_count"] >= 4 else "❌"
        print(f"  {emoji} {r['name']:14s} | {company_label} | now {r['current_skill_count']} skills")


if __name__ == "__main__":
    asyncio.run(main())
