#!/usr/bin/env python3
import argparse
import urllib.request
import urllib.parse
import json
import os
import re
import sys

PB_URL = "http://localhost:8102"
EMAIL = "edelmar.edd@gmail.com"
PASS = "564712564712"

# Initial suppliers to seed
PROVEEDORES_SEMILLA = [
    {"nombre": "Ciarrocchi", "rubro": "Materiales"},
    {"nombre": "Anibal", "rubro": "Mano de obra"},
    {"nombre": "Vaquero", "rubro": "Materiales / Herrajes"},
    {"nombre": "Franco Flete", "rubro": "Flete"},
    {"nombre": "Fasano", "rubro": "Materiales"},
    {"nombre": "Sabrina", "rubro": "Mano de obra"},
    {"nombre": "EDES", "rubro": "Servicios"},
    {"nombre": "Contadora", "rubro": "Servicios"},
    {"nombre": "Ricardo", "rubro": "Mano de obra / Flete"},
    {"nombre": "Beto", "rubro": "Mano de obra"},
    {"nombre": "ElectriK", "rubro": "Materiales"},
    {"nombre": "Ferretería (Insumos)", "rubro": "Materiales"}
]

# Keyword mappings for egresos details to suppliers
EGRESO_KEYWORDS = [
    (r"\bci[ar]*occhi\b", "Ciarrocchi"),
    (r"\banibal\b", "Anibal"),
    (r"\bvaquero\b", "Vaquero"),
    (r"\bfranco\s+flete\b|\bflete\b|\bfranco\b|\btraslado\b", "Franco Flete"),
    (r"\bfasano\b", "Fasano"),
    (r"\bsabrina\b", "Sabrina"),
    (r"\bedes\b", "EDES"),
    (r"\bcontadora\b", "Contadora"),
    (r"\bricardo\b", "Ricardo"),
    (r"\bbeto\b", "Beto"),
    (r"\belectrik\b", "ElectriK"),
    (r"\bferreter[ií]a\b", "Ferretería (Insumos)")
]

def req(method, path, token=None, body=None):
    headers = {"Content-Type": "application/json"}
    if token:
        headers["Authorization"] = token
    data = json.dumps(body).encode() if body is not None else None
    r = urllib.request.Request(f"{PB_URL}{path}", data=data, method=method, headers=headers)
    try:
        with urllib.request.urlopen(r) as resp:
            return resp.status, json.loads(resp.read().decode() or "{}")
    except Exception as e:
        if hasattr(e, 'read'):
            return e.code, json.loads(e.read().decode() or "{}")
        raise e

def auth_admin():
    code, data = req("POST", "/api/collections/_superusers/auth-with-password",
                     body={"identity": EMAIL, "password": PASS})
    if code != 200:
        raise Exception(f"Admin auth failed: {code} {data}")
    return data["token"]

def get_all(token, collection, filter_expr=None):
    items = []
    page = 1
    while True:
        qs = {"page": page, "perPage": 200}
        if filter_expr:
            qs["filter"] = filter_expr
        qs_str = urllib.parse.urlencode(qs)
        code, data = req("GET", f"/api/collections/{collection}/records?{qs_str}", token=token)
        if code != 200:
            raise Exception(f"Get all {collection} failed: {code} {data}")
        items.extend(data.get("items", []))
        if page >= data.get("totalPages", 1):
            break
        page += 1
    return items

def extract_pedido_refs(numero_pedido_str):
    if not numero_pedido_str:
        return []
    
    # 1. Look for full codes like YYMM-NNNN (e.g. 2605-0406)
    full_codes = re.findall(r"\b\d{4}-\d{4}\b", numero_pedido_str)
    if full_codes:
        return [("code", c) for c in full_codes]
        
    # 2. Extract sequences of numbers
    parts = re.split(r"\+", numero_pedido_str)
    refs = []
    for part in parts:
        nums = re.findall(r"\b\d+\b", part)
        for num in nums:
            refs.append(("num", num.lstrip("0")))
    return refs

def main():
    parser = argparse.ArgumentParser(description="Conciliacion de ingresos y egresos de caja")
    parser.add_argument("--apply", action="store_true", help="Aplica los cambios reales a la base de datos")
    args = parser.parse_args()

    print("=== SCRIPT DE CONCILIACIÓN DE CAJA ===")
    print(f"URL de base de datos: {PB_URL}")
    print(f"Modo: {'APLICAR CAMBIOS (PRODUCCIÓN)' if args.apply else 'SIMULACIÓN (DRY-RUN)'}")
    print("")

    token = auth_admin()
    
    # 1. Cargar datos existentes
    print("Cargando colecciones...")
    movimientos = get_all(token, "movimientos")
    pedidos = get_all(token, "pedidos")
    pagos = get_all(token, "pagos")
    cuentas = get_all(token, "cuentas")
    proveedores = get_all(token, "proveedores")
    
    print(f"  Movimientos cargados: {len(movimientos)}")
    print(f"  Pedidos cargados: {len(pedidos)}")
    print(f"  Pagos cargados: {len(pagos)}")
    print(f"  Cuentas cargadas: {len(cuentas)}")
    print(f"  Proveedores cargados: {len(proveedores)}")
    print("")

    # Map accounts name to ID
    cuentas_by_name = {c["nombre"].strip().lower(): c["id"] for c in cuentas}
    
    # Index orders
    pedidos_by_code = {p["codigo"].strip(): p for p in pedidos if p.get("codigo")}
    pedidos_by_num = {str(p["numero"]): p for p in pedidos if p.get("numero") is not None}

    # Index existing suppliers by name
    proveedores_by_name = {p["nombre"].strip().lower(): p for p in proveedores}

    # Track which movements are already linked to a payment
    linked_mov_ids = set()
    for pago in pagos:
        mov_id = pago.get("movimiento")
        if mov_id:
            linked_mov_ids.add(mov_id)

    # 2. Carga/Semillado de Proveedores
    print("--- 1. Carga de Proveedores ---")
    seeded_proveedores_by_name = {}
    for prov_data in PROVEEDORES_SEMILLA:
        name_lower = prov_data["nombre"].lower()
        if name_lower in proveedores_by_name:
            seeded_proveedores_by_name[name_lower] = proveedores_by_name[name_lower]["id"]
            # print(f"  Proveedor '{prov_data['nombre']}' ya existe.")
        else:
            if not args.apply:
                print(f"  [Dry-run] Creará proveedor: {prov_data['nombre']} (Rubro: {prov_data['rubro']})")
                seeded_proveedores_by_name[name_lower] = "mock_id_" + name_lower
            else:
                payload = {
                    "nombre": prov_data["nombre"],
                    "rubro": prov_data["rubro"],
                    "activo": True
                }
                code, res = req("POST", "/api/collections/proveedores/records", token=token, body=payload)
                if code == 200:
                    print(f"  [OK] Creado proveedor: {res['nombre']} (ID: {res['id']})")
                    seeded_proveedores_by_name[name_lower] = res["id"]
                    # Add to memory list
                    proveedores_by_name[name_lower] = res
                else:
                    print(f"  [ERROR] Falló crear proveedor '{prov_data['nombre']}': {code} {res}")
    print("")

    # 3. Conciliación de Egresos
    print("--- 2. Conciliación de Egresos (Gastos) ---")
    egresos_linked = 0
    for m in movimientos:
        if m.get("tipo") != "egreso":
            continue
        
        # Skip if already has a provider
        if m.get("proveedor"):
            continue
            
        detalle_str = m.get("detalle", "")
        detalle_lower = detalle_str.lower()
        
        matched_prov_name = None
        for pattern, prov_name in EGRESO_KEYWORDS:
            if re.search(pattern, detalle_lower):
                matched_prov_name = prov_name
                break
                
        if matched_prov_name:
            prov_id = seeded_proveedores_by_name.get(matched_prov_name.lower())
            if prov_id:
                egresos_linked += 1
                if not args.apply:
                    print(f"  [Dry-run] Vinculará egreso {m['id']} (Detalle: '{detalle_str}', Monto: {m['monto']}) -> Proveedor: {matched_prov_name}")
                else:
                    payload = {"proveedor": prov_id}
                    code, res = req("PATCH", f"/api/collections/movimientos/records/{m['id']}", token=token, body=payload)
                    if code == 200:
                        print(f"  [OK] Vinculado egreso {m['id']} ('{detalle_str}') -> {matched_prov_name}")
                    else:
                        print(f"  [ERROR] Falló vincular egreso {m['id']}: {code} {res}")
    
    print(f"Total egresos vinculados o por vincular: {egresos_linked}")
    print("")

    # 4. Conciliación de Ingresos
    print("--- 3. Conciliación de Ingresos ---")
    ingresos_linked = 0
    ambiguous_ingresos = 0
    
    for m in movimientos:
        if m.get("tipo") != "ingreso":
            continue
            
        num_ped_str = m.get("numero_pedido", "").strip()
        if not num_ped_str:
            continue
            
        if m["id"] in linked_mov_ids:
            continue
            
        refs = extract_pedido_refs(num_ped_str)
        matched_pedidos = []
        for ref_type, ref_val in refs:
            if ref_type == "code":
                p = pedidos_by_code.get(ref_val)
                if p:
                    matched_pedidos.append(p)
            elif ref_type == "num":
                p = pedidos_by_num.get(ref_val)
                if p:
                    matched_pedidos.append(p)
                    
        # Remove duplicate matched pedidos in list
        seen_ped_ids = set()
        unique_matched_pedidos = []
        for p in matched_pedidos:
            if p["id"] not in seen_ped_ids:
                seen_ped_ids.add(p["id"])
                unique_matched_pedidos.append(p)
                
        if len(unique_matched_pedidos) == 1:
            pedido = unique_matched_pedidos[0]
            
            # Find account ID
            cuenta_name = m.get("cuenta", "").strip().lower()
            cuenta_id = cuentas_by_name.get(cuenta_name)
            if not cuenta_id:
                # Fallback to "Efectivo"
                cuenta_id = cuentas_by_name.get("efectivo")
                
            if not cuenta_id:
                print(f"  [ERROR] No se encontró cuenta '{m.get('cuenta')}' ni la cuenta default 'Efectivo'. Saltando.")
                continue
                
            ingresos_linked += 1
            if not args.apply:
                print(f"  [Dry-run] Vinculará ingreso {m['id']} (Ref: '{num_ped_str}', Monto: {m['monto']}) -> Pedido: {pedido['codigo']} | Cliente ID: {pedido['cliente']}")
            else:
                # 1. Crear pago
                # Convert fecha "YYYY-MM-DD" to ISO datetime format for payments
                pago_fecha = f"{m.get('fecha')} 12:00:00.000Z"
                pago_payload = {
                    "monto": m["monto"],
                    "cuenta": cuenta_id,
                    "fecha": pago_fecha,
                    "cliente": pedido["cliente"],
                    "pedido": pedido["id"],
                    "nota": m.get("detalle") or "Pago (Importado)"
                }
                
                code_create, pago_res = req("POST", "/api/collections/pagos/records", token=token, body=pago_payload)
                if code_create == 200:
                    pago_id = pago_res["id"]
                    new_mov_id = pago_res.get("movimiento")
                    print(f"  [OK] Creado pago {pago_id} (Monto: {m['monto']}) -> Pedido: {pedido['codigo']}. Movimiento duplicado creado: {new_mov_id}")
                    
                    # 2. Re-asociar pago a movimiento original
                    pago_patch_payload = {
                        "movimiento": m["id"]
                    }
                    code_patch, patch_res = req("PATCH", f"/api/collections/pagos/records/{pago_id}", token=token, body=pago_patch_payload)
                    if code_patch == 200:
                        print(f"    [OK] Pago {pago_id} re-asociado a movimiento original {m['id']}.")
                    else:
                        print(f"    [ERROR] Falló re-asociar pago {pago_id} a movimiento original {m['id']}: {code_patch} {patch_res}")
                        
                    # 3. Eliminar movimiento duplicado
                    if new_mov_id:
                        code_del, del_res = req("DELETE", f"/api/collections/movimientos/records/{new_mov_id}", token=token)
                        if code_del in (200, 204):
                            print(f"    [OK] Eliminado movimiento duplicado {new_mov_id}.")
                        else:
                            print(f"    [ERROR] Falló eliminar movimiento duplicado {new_mov_id}: {code_del} {del_res}")
                else:
                    print(f"  [ERROR] Falló crear pago para movimiento {m['id']}: {code_create} {pago_res}")
                    
        elif len(unique_matched_pedidos) > 1:
            ambiguous_ingresos += 1
            ped_codes = ", ".join([p["codigo"] for p in unique_matched_pedidos])
            print(f"  [AMBIGUO] Ingreso {m['id']} (Ref: '{num_ped_str}', Monto: {m['monto']}) coincide con varios pedidos: {ped_codes}")

    print("")
    print("Resumen:")
    print(f"  Egresos vinculados / por vincular: {egresos_linked}")
    print(f"  Ingresos vinculados / por vincular: {ingresos_linked}")
    print(f"  Ingresos ambiguos (no modificados): {ambiguous_ingresos}")
    print("Done.")

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"FATAL: {e}", file=sys.stderr)
        sys.exit(1)
