#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Copy contact custom field Sucursal into each linked opportunity."""

import argparse
import csv
import json
import os
import sqlite3
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if ROOT_DIR not in sys.path:
    sys.path.insert(0, ROOT_DIR)

import db  # noqa: E402
import script_audit  # noqa: E402
import sync_engine  # noqa: E402


BRAND_LOCATION_ID = "GbKkBpCmKu2QmloKFHy3"
FIELD_NAME = "Sucursal"


def clean(value):
    return str(value or "").strip()


def load_accounts():
    if not os.path.exists(sync_engine.CSV_PATH):
        raise FileNotFoundError(f"No se encontro el CSV de tokens: {sync_engine.CSV_PATH}")

    accounts = {}
    with open(sync_engine.CSV_PATH, mode="r", encoding="utf-8-sig", newline="") as fh:
        for row in csv.DictReader(fh):
            location_id = clean(row.get("Location_ID"))
            token = clean(row.get("API_token"))
            if not location_id or not token:
                continue
            accounts[location_id] = {
                "location_id": location_id,
                "nombre": clean(row.get("Nombre")) or location_id,
                "token": token,
                "type": "brand" if location_id == BRAND_LOCATION_ID else "branch",
            }
    return list(accounts.values())


def select_accounts(args):
    accounts = load_accounts()
    if args.location:
        selected = [account for account in accounts if account["location_id"] == args.location]
        if not selected:
            raise SystemExit(f"Location {args.location} no existe en el CSV de tokens")
        return selected

    if args.all:
        if args.include_main:
            return accounts
        return [account for account in accounts if account.get("type") == "branch"]

    raise SystemExit("Especifica --location <id> o --all. Usa --apply para escribir en GHL.")


def get_schema_field_id(location_id, token, object_key):
    schema = sync_engine.ghl_client.get_object_schema(token, location_id, object_key)
    return schema.get(FIELD_NAME)


def get_custom_field_value(record, field_id):
    # GHL devuelve customFields con keys distintas según el objeto:
    #   contacts → "value"
    #   opportunities → "fieldValue"
    # Algunos endpoints históricos también devuelven "fieldValueString".
    if not field_id:
        return None

    custom_fields = record.get("customFields") or record.get("custom_fields") or []
    if isinstance(custom_fields, dict):
        return custom_fields.get(field_id)

    for field in custom_fields:
        if not isinstance(field, dict):
            continue
        if field.get("id") != field_id and field.get("fieldId") != field_id:
            continue
        for key in ("value", "fieldValue", "fieldValueString"):
            val = field.get(key)
            if val is not None:
                return val
        return None
    return None


def parse_custom_fields(raw_json):
    try:
        value = json.loads(raw_json or "[]")
    except json.JSONDecodeError:
        return []
    return value if isinstance(value, list) else []


def load_contact_sucursal_by_id(location_id, contact_field_id):
    if not os.path.exists(db.DB_PATH):
        raise FileNotFoundError(f"No existe {db.DB_PATH}. Ejecuta una sincronizacion previa.")
    if not contact_field_id:
        return {}

    conn = sqlite3.connect(db.DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        rows = conn.execute(
            """
            SELECT id, custom_fields_json
            FROM contacts
            WHERE location_id = ?
            """,
            (location_id,),
        ).fetchall()
    finally:
        conn.close()

    values = {}
    for row in rows:
        for field in parse_custom_fields(row["custom_fields_json"]):
            if field.get("id") != contact_field_id:
                continue
            sucursal = clean(field.get("value"))
            if sucursal:
                values[row["id"]] = sucursal
            break
    return values


def iter_opportunities(location_id, token, limit):
    page = 1
    seen = 0
    while limit is None or seen < limit:
        page_limit = 100 if limit is None else min(100, limit - seen)
        data = sync_engine.ghl_client.search_opportunities(token, location_id, limit=page_limit, page=page)
        batch = data.get("opportunities", [])
        if not batch:
            break
        for opportunity in batch:
            yield opportunity
            seen += 1
            if limit is not None and seen >= limit:
                break
        total_reported = data.get("total", 0) or 0
        if len(batch) < page_limit or (total_reported > 0 and seen >= total_reported):
            break
        page += 1


def update_opportunity_sucursal(token, opportunity_id, opportunity_field_id, sucursal):
    return sync_engine.ghl_client.update_opportunity(
        token,
        opportunity_id,
        {"customFields": [{"id": opportunity_field_id, "value": sucursal}]},
    )


def process_location(account, args):
    location_id = account["location_id"]
    token = account["token"]
    dry_run = not args.apply
    stats = {
        "opportunities": 0,
        "contacts_missing": 0,
        "contact_sucursal_missing": 0,
        "already_ok": 0,
        "planned": 0,
        "updated": 0,
        "errors": 0,
    }

    print(f"\n=== {account['nombre']} ({location_id}) ===")
    contact_field_id = get_schema_field_id(location_id, token, "contact")
    opportunity_field_id = get_schema_field_id(location_id, token, "opportunity")
    if not contact_field_id:
        print("SKIP: campo Sucursal no existe en schema de contact")
        stats["errors"] += 1
        return stats
    if not opportunity_field_id:
        print("SKIP: campo Sucursal no existe en schema de opportunity")
        stats["errors"] += 1
        return stats

    contact_sucursal_by_id = load_contact_sucursal_by_id(location_id, contact_field_id)
    print(f"Contactos con Sucursal en SQLite: {len(contact_sucursal_by_id)}")

    for opportunity in iter_opportunities(location_id, token, args.limit):
        if not script_audit.wait_if_paused_or_stopped(args.run_id):
            break

        stats["opportunities"] += 1
        opportunity_id = opportunity.get("id")
        contact_id = opportunity.get("contactId") or opportunity.get("contact_id")
        if not opportunity_id:
            stats["errors"] += 1
            continue
        if not contact_id:
            stats["contacts_missing"] += 1
            continue

        target_sucursal = contact_sucursal_by_id.get(contact_id)
        if not target_sucursal:
            stats["contact_sucursal_missing"] += 1
            continue

        current_sucursal = clean(get_custom_field_value(opportunity, opportunity_field_id))
        if current_sucursal == target_sucursal:
            stats["already_ok"] += 1
            continue

        change_id = script_audit.record_change(
            args.run_id,
            location_id,
            "opportunity",
            opportunity_id,
            opportunity_field_id,
            FIELD_NAME,
            current_sucursal,
            target_sucursal,
        )

        label = opportunity.get("name") or opportunity.get("contactName") or opportunity_id
        if dry_run:
            stats["planned"] += 1
            print(f"PLAN opp {opportunity_id} | {label} | {current_sucursal!r} -> {target_sucursal!r}")
            continue

        try:
            update_opportunity_sucursal(token, opportunity_id, opportunity_field_id, target_sucursal)
            script_audit.mark_change(change_id, "applied")
            stats["updated"] += 1
            print(f"OK opp {opportunity_id} | {label} | {current_sucursal!r} -> {target_sucursal!r}")
        except Exception as exc:
            script_audit.mark_change(change_id, "failed", str(exc))
            stats["errors"] += 1
            print(f"ERROR opp {opportunity_id}: {exc}")

    print(f"Stats {location_id}: {stats}")
    return stats


def add_stats(total, current):
    for key, value in current.items():
        total[key] = total.get(key, 0) + value


def parse_args():
    parser = argparse.ArgumentParser(
        description="Copia el campo Sucursal del contacto hacia oportunidades vinculadas. Dry-run por defecto."
    )
    parser.add_argument("--location", help="Location ID especifico")
    parser.add_argument("--all", action="store_true", help="Procesa todas las sucursales del CSV")
    parser.add_argument("--include-main", action="store_true", help="Incluye la cuenta de marca con --all")
    parser.add_argument("--apply", action="store_true", help="Aplica PUT reales en GHL. Sin este flag solo simula")
    parser.add_argument("--limit", type=int, help="Limite de oportunidades por location")
    parser.add_argument("--workers", type=int, default=1, help="Paralelismo interno para --all. Default: 1")
    parser.add_argument("--run-id", help="Audit run ID suministrado por dashboard")
    args = parser.parse_args()

    if args.limit is not None and args.limit < 1:
        raise SystemExit("--limit debe ser mayor a 0")
    if args.workers < 1:
        raise SystemExit("--workers debe ser mayor a 0")
    return args


def main():
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8")

    args = parse_args()
    accounts = select_accounts(args)
    dry_run = not args.apply

    print("=" * 70)
    print("SYNC CONTACT SUCURSAL -> OPPORTUNITY")
    print("=" * 70)
    print(f"Modo: {'DRY-RUN (sin cambios)' if dry_run else 'APPLY (PUT real en GHL)'}")
    print("Fuente contacto: SQLite local; destino: oportunidades live GHL")
    print(f"Locations: {len(accounts)} | Workers: {min(args.workers, len(accounts))}")

    grand_total = {
        "opportunities": 0,
        "contacts_missing": 0,
        "contact_sucursal_missing": 0,
        "already_ok": 0,
        "planned": 0,
        "updated": 0,
        "errors": 0,
    }

    if args.workers > 1 and len(accounts) > 1:
        with ThreadPoolExecutor(max_workers=min(args.workers, len(accounts))) as executor:
            futures = {executor.submit(process_location, account, args): account for account in accounts}
            for future in as_completed(futures):
                add_stats(grand_total, future.result())
    else:
        for account in accounts:
            add_stats(grand_total, process_location(account, args))

    print("\n" + "=" * 70)
    print(f"TOTAL: {grand_total}")
    if dry_run:
        print("Dry-run terminado. Usa --apply para escribir los cambios planeados.")
    if grand_total.get("errors"):
        raise SystemExit(1)


if __name__ == "__main__":
    main()
