#!/usr/bin/env python3
"""Reset generated reconciliation staging items so they can be rebuilt.

Safe by default: dry-run only and only affects generated external candidates
from Jasmin/Odoo/Packlink in open/needs_review/ignored states. It does not
remove opportunities, commercial documents, payment proofs, operation links or
external system records.

Examples:
  PYTHONPATH=. python scripts/reset_reconciliation_generated.py
  PYTHONPATH=. python scripts/reset_reconciliation_generated.py --apply
  PYTHONPATH=. python scripts/reset_reconciliation_generated.py --source jasmin --source odoo --apply
  PYTHONPATH=. python scripts/reset_reconciliation_generated.py --days 30 --apply
  PYTHONPATH=. python scripts/reset_reconciliation_generated.py --include-manual --apply
"""
from __future__ import annotations

import argparse
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List

from sqlalchemy import text

from app.db import engine, init_db
from app.reconciliation_service import ensure_reconciliation_schema

DEFAULT_SOURCES = ["jasmin", "odoo", "packlink"]
DEFAULT_STATUSES = ["open", "needs_review", "ignored"]


def _backup_table_name() -> str:
    return "reconciliation_items_reset_backup_" + datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")


def _build_where(args: argparse.Namespace) -> tuple[str, Dict[str, Any]]:
    sources = list(args.source or DEFAULT_SOURCES)
    if args.include_manual and "manual" not in sources:
        sources.append("manual")
    statuses = list(args.status or DEFAULT_STATUSES)
    params: Dict[str, Any] = {"sources": sources, "statuses": statuses}
    clauses = [
        "source_system = ANY(CAST(:sources AS TEXT[]))",
        "status = ANY(CAST(:statuses AS TEXT[]))",
    ]
    # Extra guard: never touch items already linked to an opportunity unless
    # the operator explicitly changes the status list and unlinks manually.
    clauses.append("opportunity_id IS NULL")

    if args.days is not None:
        days = max(int(args.days), 1)
        cutoff = (datetime.now(timezone.utc).date() - timedelta(days=days - 1)).isoformat()
        params["cutoff"] = cutoff
        clauses.append("(document_date IS NULL OR document_date >= CAST(:cutoff AS DATE))")
    return " AND ".join(clauses), params


def _summarize(where_sql: str, params: Dict[str, Any], limit: int) -> Dict[str, Any]:
    with engine.begin() as conn:
        counts = conn.execute(text(f"""
            SELECT source_system, external_type, status, COUNT(*) AS total
            FROM reconciliation_items
            WHERE {where_sql}
            GROUP BY source_system, external_type, status
            ORDER BY source_system, external_type, status
        """), params).mappings().all()
        rows = conn.execute(text(f"""
            SELECT id::text, source_system, external_type, status, document_number,
                   customer_name, customer_tax_id, document_date, amount, title
            FROM reconciliation_items
            WHERE {where_sql}
            ORDER BY updated_at DESC, created_at DESC
            LIMIT :limit
        """), {**params, "limit": int(limit)}).mappings().all()
    return {"counts": [dict(r) for r in counts], "items": [dict(r) for r in rows]}


def _apply_reset(where_sql: str, params: Dict[str, Any]) -> Dict[str, Any]:
    backup_table = _backup_table_name()
    # backup_table is generated internally from digits/underscore only.
    with engine.begin() as conn:
        total = conn.execute(text(f"SELECT COUNT(*) FROM reconciliation_items WHERE {where_sql}"), params).scalar() or 0
        conn.execute(text(f"CREATE TABLE {backup_table} AS SELECT * FROM reconciliation_items WHERE {where_sql}"), params)
        deleted = conn.execute(text(f"DELETE FROM reconciliation_items WHERE {where_sql}"), params).rowcount or 0
    return {"matched": int(total), "deleted": int(deleted), "backup_table": backup_table}


def main() -> None:
    parser = argparse.ArgumentParser(description="Reset generated reconciliation candidates and keep a DB backup table.")
    parser.add_argument("--source", action="append", choices=["jasmin", "odoo", "packlink", "manual"], help="source_system to reset; repeatable. Default: jasmin, odoo, packlink")
    parser.add_argument("--status", action="append", choices=["open", "needs_review", "ignored"], help="status to reset; repeatable. Default: open, needs_review, ignored")
    parser.add_argument("--days", type=int, help="only reset candidates inside the last N days; default is all dates")
    parser.add_argument("--include-manual", action="store_true", help="also include source_system=manual; use with care")
    parser.add_argument("--limit", type=int, default=50, help="preview sample size")
    parser.add_argument("--apply", action="store_true", help="delete matched staging rows after creating a backup table")
    args = parser.parse_args()

    init_db()
    ensure_reconciliation_schema()
    where_sql, params = _build_where(args)
    summary = _summarize(where_sql, params, args.limit)

    print("Alvo do reset:")
    print(f"  fontes: {', '.join(params['sources'])}")
    print(f"  estados: {', '.join(params['statuses'])}")
    print("  proteção: opportunity_id IS NULL")
    if args.days is not None:
        print(f"  janela: >= {params['cutoff']} ({args.days} dias)")
    print("\nContagens:")
    if not summary["counts"]:
        print("  0 itens encontrados")
    for row in summary["counts"]:
        print(f"  {row['source_system']} · {row['external_type']} · {row['status']}: {row['total']}")

    print("\nAmostra:")
    for row in summary["items"]:
        print(
            f"  {row.get('document_date') or '-'} · {row.get('source_system')} · {row.get('external_type')} · "
            f"{row.get('status')} · {row.get('document_number') or '-'} · {row.get('customer_name') or '-'}"
        )

    if not args.apply:
        print("\nDry-run. Para aplicar: repetir com --apply")
        return

    result = _apply_reset(where_sql, params)
    print("\nReset aplicado:")
    print(f"  encontrados: {result['matched']}")
    print(f"  apagados: {result['deleted']}")
    print(f"  backup: {result['backup_table']}")


if __name__ == "__main__":
    main()
