#!/usr/bin/env python3
"""Repair/suggest cross-conversation B2B opportunity links.

Handles the pattern where compras/geral creates an opportunity and later
financeiro/contabilidade sends payment in a new Chatwoot conversation.

Read-only by default. With --apply, only strong document-reference matches are
moved automatically. Same-domain matches with more than one candidate are marked
for operator association.
"""
from __future__ import annotations

import argparse
import json
from typing import Any, Dict, List

from sqlalchemy import text

from app.db import engine
from app.opportunity_service import get_task_context
from app.company_opportunity_linking import (
    associate_task_to_opportunity,
    find_document_owner_opportunity_for_task,
    manual_link_candidates_for_task,
    mark_task_ambiguous_with_candidates,
    task_document_references,
)


def _json(value: Any) -> str:
    return json.dumps(value or {}, ensure_ascii=False, default=str)


def task_ids(days: int, limit: int) -> List[str]:
    with engine.begin() as conn:
        rows = conn.execute(text("""
            SELECT t.id::text
            FROM tasks t
            LEFT JOIN raw_events re ON re.id = t.raw_event_id
            LEFT JOIN messages m ON m.id = t.message_id
            WHERE t.created_at >= now() - make_interval(days => :days)
              AND COALESCE(t.status, '') IN ('pending','done','skipped')
              AND COALESCE(t.action_code, '') IN ('CONFIRM_PAYMENT','SEND_PROFORMA','SEND_INVOICE','SEND_QUOTE','PREPARE_ORDER','CREATE_SHIPMENT')
              AND (
                    COALESCE(re.payload::text, '') ~* '(ORC|Orçamento|orcamento|FA|Fatura|Factura)[ .:/-]*20[0-9]{2}[ ./-]*[0-9]{1,6}'
                 OR COALESCE(m.raw_body, '') ~* '(ORC|Orçamento|orcamento|FA|Fatura|Factura)[ .:/-]*20[0-9]{2}[ ./-]*[0-9]{1,6}'
                 OR COALESCE(m.clean_body, '') ~* '(ORC|Orçamento|orcamento|FA|Fatura|Factura)[ .:/-]*20[0-9]{2}[ ./-]*[0-9]{1,6}'
                 OR COALESCE(t.note, '') ~* '(ORC|Orçamento|orcamento|FA|Fatura|Factura)[ .:/-]*20[0-9]{2}[ ./-]*[0-9]{1,6}'
              )
            ORDER BY t.created_at DESC
            LIMIT :limit
        """), {"days": int(days), "limit": int(limit)}).scalars().all()
    return [str(x) for x in rows]


def source_opportunity_document_count(opportunity_id: str) -> int:
    if not opportunity_id:
        return 0
    with engine.begin() as conn:
        return int(conn.execute(text("""
            SELECT COUNT(*)::int
            FROM commercial_documents
            WHERE opportunity_id = CAST(:opportunity_id AS UUID)
              AND COALESCE(is_active, TRUE) = TRUE
        """), {"opportunity_id": opportunity_id}).scalar() or 0)


def close_empty_duplicate(source_opportunity_id: str, target_opportunity_id: str, refs: List[str], apply: bool) -> int:
    if not source_opportunity_id or source_opportunity_id == target_opportunity_id:
        return 0
    if source_opportunity_document_count(source_opportunity_id) > 0:
        return 0
    if not apply:
        return 0
    meta = {
        "closed_as_duplicate_by": "repair_cross_conversation_company_links",
        "duplicate_of_opportunity_id": target_opportunity_id,
        "document_references": refs,
    }
    with engine.begin() as conn:
        res = conn.execute(text("""
            UPDATE opportunities
            SET status = 'closed',
                closed_at = COALESCE(closed_at, now()),
                metadata = COALESCE(metadata, '{}'::jsonb) || CAST(:metadata AS JSONB),
                updated_at = now()
            WHERE id = CAST(:source AS UUID)
              AND status = 'open'
        """), {"source": source_opportunity_id, "metadata": _json(meta)})
    return int(res.rowcount or 0)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--days", type=int, default=14)
    parser.add_argument("--limit", type=int, default=200)
    parser.add_argument("--apply", action="store_true")
    args = parser.parse_args()

    ids = task_ids(args.days, args.limit)
    print(f"apply={args.apply} scanned={len(ids)} days={args.days}")

    strong = 0
    moved = 0
    closed = 0
    ambiguous = 0

    for task_id in ids:
        task = get_task_context(task_id)
        if not task:
            continue
        refs = task_document_references(task)
        target = find_document_owner_opportunity_for_task(task)
        source_id = str(task.get("opportunity_id") or "")
        if target:
            target_id = str(target.get("id") or target.get("opportunity_id") or "")
            if target_id and target_id != source_id:
                strong += 1
                print()
                print(f"STRONG_DOCUMENT_MATCH task={task_id}")
                print(f"refs={refs}")
                print(f"source_opp={source_id or '—'}")
                print(f"target_opp={target_id} title={target.get('title')} doc={target.get('document_number')}")
                if args.apply:
                    result = associate_task_to_opportunity(task_id, target_id, actor="repair_cross_conversation_company_links")
                    moved += result.get("tasks", 0)
                    closed += close_empty_duplicate(source_id, target_id, refs, apply=True)
            continue

        candidates = manual_link_candidates_for_task(task)
        if len(candidates) > 1:
            ambiguous += 1
            print()
            print(f"AMBIGUOUS_COMPANY_MATCH task={task_id} candidates={len(candidates)} refs={refs}")
            for c in candidates[:5]:
                print(f"- opp={c.get('opportunity_id')} title={c.get('title')} doc={c.get('document_number')} reason={c.get('reason')}")
            if args.apply:
                mark_task_ambiguous_with_candidates(
                    task_id,
                    reason="multiple_company_domain_candidates",
                    candidates=candidates,
                    created_by="repair_cross_conversation_company_links",
                )

    print()
    print("SUMMARY")
    print(f"strong_document_matches={strong}")
    print(f"tasks_moved={moved}")
    print(f"duplicates_closed={closed}")
    print(f"ambiguous_marked_or_listed={ambiguous}")
    if not args.apply:
        print("dry_run=True")
    return 0


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