"""Seed de datos para el CRM en desarrollo.

Idempotente: se puede correr varias veces sin duplicar. Puebla:
  1. Tenant dev (core.tenants id=1) — requerido por la FK tenant_id de las tablas crm.
  2. Embudo por defecto "Ventas" + 6 etapas (catálogo del pipeline).
  3. Datos de ejemplo: cuentas, contactos, prospectos, oportunidades y actividades.

Ejecutar dentro del contenedor backend:
    docker compose exec backend python seed_crm.py

Los datos usan valores dummy (RFC XAXX010101000, etc.); no incluir datos reales.
"""

from datetime import datetime, timedelta, timezone

from api.v1.modules.core.tenants.models import Tenant, TenantType
from api.v1.modules.crm.accounts.models import Account
from api.v1.modules.crm.activities.models import Activity
from api.v1.modules.crm.addresses.models import Address
from api.v1.modules.crm.contacts.models import Contact
from api.v1.modules.crm.documents.models import Document
from api.v1.modules.crm.leads.models import Lead
from api.v1.modules.crm.opportunities.models import Opportunity
from api.v1.modules.crm.pipelines.models import Pipeline, PipelineStage
from api.v1.modules.crm.quotes.models import Quote, QuoteItem
from api.v1.modules.crm.service_requests.models import ServiceRequest
from api.v1.modules.crm.suppliers.models import Supplier
from api.v1.modules.ops.shipments.models import Shipment, ShipmentDocument, ShipmentEvent
from api.v1.modules.ops.shipments import service as shipments_service
from api.v1.modules.ops.shipments.dto import ShipmentCloseInput, ShipmentEventDecisionInput
from api.v1.modules.fin.invoices.models import Invoice
from api.v1.modules.fin.invoices import service as invoices_service
from api.v1.modules.fin.invoices.dto import PaymentCreate
from api.v1.modules.core.permissions.models import CompanyRole, Permission, RolePermission
from api.v1.modules.core.permissions.service import PermissionService
# Efecto secundario: poblar el PermissionRegistry con los permisos de cada dominio
import api.v1.modules.crm.permissions  # noqa: F401
import api.v1.modules.ops.permissions  # noqa: F401
import api.v1.modules.fin.permissions  # noqa: F401
from core.database import CoreSessionLocal

# Deben coincidir con DEV_LOCAL_AUTH_TENANT_ID / DEV_LOCAL_AUTH_COMPANY_ID
TENANT_ID = 1
COMPANY_ID = 1

RFC_DUMMY = "XAXX010101000"  # RFC genérico dummy (política de datos)

STAGE_DEFS = [
    # (nombre, probabilidad, is_won, is_lost)
    ("Prospecto", 10, False, False),
    ("Contactado", 25, False, False),
    ("Propuesta", 50, False, False),
    ("Negociación", 75, False, False),
    ("Ganada", 100, True, False),
    ("Perdida", 0, False, True),
]


def ensure_tenant(db) -> Tenant:
    tenant = db.query(Tenant).filter(Tenant.id == TENANT_ID).first()
    if tenant:
        print(f"• Tenant id={TENANT_ID} ya existe ({tenant.name})")
        return tenant
    tenant = Tenant(
        id=TENANT_ID,
        name="Aduanasoft (dev)",
        slug="dev",
        keycloak_realm="master",
        type=TenantType.SHARED,
        is_active=True,
    )
    db.add(tenant)
    db.commit()
    # Alinear la secuencia para que futuros inserts no colisionen con el id explícito
    db.execute(
        __import__("sqlalchemy").text(
            "SELECT setval('core.tenants_id_seq', (SELECT MAX(id) FROM core.tenants))"
        )
    )
    db.commit()
    print(f"✓ Tenant dev creado (id={TENANT_ID})")
    return tenant


def ensure_pipeline(db):
    pipeline = (
        db.query(Pipeline)
        .filter(
            Pipeline.tenant_id == TENANT_ID,
            Pipeline.company_id == COMPANY_ID,
            Pipeline.deleted_at.is_(None),
        )
        .first()
    )
    if pipeline:
        stages = (
            db.query(PipelineStage)
            .filter(
                PipelineStage.pipeline_id == pipeline.id,
                PipelineStage.deleted_at.is_(None),
            )
            .order_by(PipelineStage.position.asc())
            .all()
        )
        print(f"• Embudo '{pipeline.name}' ya existe ({len(stages)} etapas)")
        return pipeline, stages

    pipeline = Pipeline(name="Ventas", is_default=True, tenant_id=TENANT_ID, company_id=COMPANY_ID)
    db.add(pipeline)
    db.flush()
    stages = []
    for position, (name, probability, is_won, is_lost) in enumerate(STAGE_DEFS):
        stage = PipelineStage(
            pipeline_id=pipeline.id,
            name=name,
            position=position,
            probability=probability,
            is_won=is_won,
            is_lost=is_lost,
            tenant_id=TENANT_ID,
            company_id=COMPANY_ID,
        )
        db.add(stage)
        stages.append(stage)
    db.commit()
    for stage in stages:
        db.refresh(stage)
    print(f"✓ Embudo 'Ventas' + {len(stages)} etapas creados")
    return pipeline, stages


def seed_sample_data(db, pipeline, stages) -> None:
    has_accounts = (
        db.query(Account)
        .filter(
            Account.tenant_id == TENANT_ID,
            Account.company_id == COMPANY_ID,
            Account.deleted_at.is_(None),
        )
        .first()
    )
    if has_accounts:
        print("• Ya existen cuentas; se omiten los datos de ejemplo")
        return

    accounts_data = [
        dict(name="Maquiladora del Norte SA de CV", trade_name="MaqNorte", rfc=RFC_DUMMY,
             account_type="immex", status="active", city="Tijuana", state="Baja California",
             email="contacto@ejemplo.mx", phone="6640000000"),
        dict(name="Importadora Pacífico SA de CV", trade_name="ImpPacífico", rfc=RFC_DUMMY,
             account_type="importador", status="active", city="Manzanillo", state="Colima"),
        dict(name="Agencia Aduanal López y Asociados", account_type="agencia_aduanal",
             patente_aduanal="0000", status="active", city="Nuevo Laredo", state="Tamaulipas"),
        dict(name="Transportes Frontera", account_type="transportista", status="prospect",
             city="Ciudad Juárez", state="Chihuahua"),
    ]
    accounts = [Account(**d, tenant_id=TENANT_ID, company_id=COMPANY_ID) for d in accounts_data]
    db.add_all(accounts)
    db.flush()

    contacts_data = [
        dict(account=accounts[0], first_name="María", last_name="Pérez",
             job_title="Gerente de Comercio Exterior", email="maria@ejemplo.mx",
             phone="6640000001", is_primary=True),
        dict(account=accounts[1], first_name="Jorge", last_name="Ramírez",
             job_title="Director de Logística", email="jorge@ejemplo.mx", is_primary=True),
        dict(account=accounts[2], first_name="Luis", last_name="López",
             job_title="Agente Aduanal", email="luis@ejemplo.mx", is_primary=True),
    ]
    for d in contacts_data:
        account = d.pop("account")
        db.add(Contact(**d, account_id=account.id, tenant_id=TENANT_ID, company_id=COMPANY_ID))

    leads_data = [
        dict(name="Interés en módulo de pedimentos", company_name="Comercializadora Bajío",
             contact_name="Ana Torres", email="ana@ejemplo.mx", source="web", status="new",
             estimated_value=45000),
        dict(name="Demo solicitada Anexo 24", company_name="Ensambles del Golfo",
             contact_name="Pedro Gómez", source="evento", status="contacted",
             estimated_value=80000),
    ]
    for d in leads_data:
        db.add(Lead(**d, tenant_id=TENANT_ID, company_id=COMPANY_ID))

    open_stages = [s for s in stages if not s.is_won and not s.is_lost] or stages
    opportunities_data = [
        dict(name="Licencia Anexo 76 - MaqNorte", account=accounts[0], amount=150000, stage=open_stages[0]),
        dict(name="Suite completa - Importadora Pacífico", account=accounts[1], amount=320000, stage=open_stages[min(1, len(open_stages) - 1)]),
        dict(name="Módulo de saldos - López y Asociados", account=accounts[2], amount=90000, stage=open_stages[min(2, len(open_stages) - 1)]),
        dict(name="Renovación anual - MaqNorte", account=accounts[0], amount=60000, stage=open_stages[min(3, len(open_stages) - 1)]),
    ]
    for d in opportunities_data:
        account = d.pop("account")
        stage = d.pop("stage")
        db.add(Opportunity(
            **d, account_id=account.id, pipeline_id=pipeline.id, stage_id=stage.id,
            probability=stage.probability, currency="MXN", status="open",
            tenant_id=TENANT_ID, company_id=COMPANY_ID,
        ))

    now = datetime.now(timezone.utc)
    activities_data = [
        dict(activity_type="call", subject="Llamada de seguimiento MaqNorte", status="pending",
             due_date=now + timedelta(days=1), account=accounts[0]),
        dict(activity_type="meeting", subject="Demo Importadora Pacífico", status="pending",
             due_date=now + timedelta(days=3), account=accounts[1]),
        dict(activity_type="task", subject="Enviar cotización a López y Asociados",
             status="completed", completed_at=now, account=accounts[2]),
    ]
    for d in activities_data:
        account = d.pop("account")
        db.add(Activity(**d, account_id=account.id, tenant_id=TENANT_ID, company_id=COMPANY_ID))

    db.commit()
    print("✓ Datos de ejemplo: 4 cuentas, 3 contactos, 2 prospectos, 4 oportunidades, 3 actividades")


def seed_suppliers_and_related(db) -> None:
    """Proveedores + direcciones/documentos/contactos (clientes y proveedores)."""
    if db.query(Supplier).filter(Supplier.tenant_id == TENANT_ID, Supplier.company_id == COMPANY_ID, Supplier.deleted_at.is_(None)).first():
        print("• Ya existen proveedores; se omite el seed de catálogos")
        return

    suppliers_data = [
        dict(name="Naviera del Golfo SA de CV", trade_name="NavGolfo", rfc=RFC_DUMMY,
             classifications=["naviera", "agente_carga"], coverage="internacional",
             countries=["MX", "US", "PA"], ports=["Veracruz", "Manzanillo"],
             quote_currency="USD", status="active"),
        dict(name="Agencia Aduanal Reyes y Asociados", rfc=RFC_DUMMY,
             classifications=["agente_aduanal"], coverage="nacional",
             customs=["Nuevo Laredo", "Colombia"], status="active"),
    ]
    suppliers = [Supplier(**d, tenant_id=TENANT_ID, company_id=COMPANY_ID) for d in suppliers_data]
    db.add_all(suppliers)
    db.flush()

    first_account = (
        db.query(Account)
        .filter(Account.tenant_id == TENANT_ID, Account.company_id == COMPANY_ID, Account.deleted_at.is_(None))
        .order_by(Account.id.asc())
        .first()
    )

    # Direcciones (múltiples): proveedor + cliente
    db.add(Address(supplier_id=suppliers[0].id, address_type="oficina", street="Malecón 100",
                   neighborhood="Centro", postal_code="91700", city="Veracruz", state="Veracruz",
                   is_primary=True, tenant_id=TENANT_ID, company_id=COMPANY_ID))
    if first_account:
        db.add(Address(account_id=first_account.id, address_type="fiscal", street="Blvd. Industrial 500",
                       neighborhood="Otay", postal_code="22000", city="Tijuana", state="Baja California",
                       is_primary=True, tenant_id=TENANT_ID, company_id=COMPANY_ID))
        db.add(Address(account_id=first_account.id, address_type="bodega", street="Camino a la Presa 12",
                       city="Tijuana", state="Baja California", tenant_id=TENANT_ID, company_id=COMPANY_ID))

    # Documentos
    db.add(Document(supplier_id=suppliers[0].id, doc_type="constancia_fiscal",
                    name="Constancia de Situación Fiscal - NavGolfo", tenant_id=TENANT_ID, company_id=COMPANY_ID))
    if first_account:
        db.add(Document(account_id=first_account.id, doc_type="acta_constitutiva",
                        name="Acta Constitutiva - MaqNorte", tenant_id=TENANT_ID, company_id=COMPANY_ID))

    # Contacto de proveedor (con área y flags)
    db.add(Contact(supplier_id=suppliers[0].id, first_name="Rosa", last_name="Díaz", area="Ventas",
                   job_title="Ejecutiva de cuenta", email="rosa@ejemplo.mx", phone="2290000000",
                   is_primary=True, receives_quotes=True, tenant_id=TENANT_ID, company_id=COMPANY_ID))

    db.commit()
    print("✓ 2 proveedores, 3 direcciones, 2 documentos y 1 contacto de proveedor")


def seed_commercial_and_ops(db) -> None:
    """Demo del flujo comercial: Solicitud → Cotización (aceptada) → Embarque liberado."""
    if db.query(ServiceRequest).filter(
        ServiceRequest.tenant_id == TENANT_ID, ServiceRequest.company_id == COMPANY_ID,
        ServiceRequest.deleted_at.is_(None),
    ).first():
        print("• Ya existe flujo comercial; se omite")
        return

    account = (
        db.query(Account)
        .filter(Account.tenant_id == TENANT_ID, Account.company_id == COMPANY_ID, Account.deleted_at.is_(None))
        .order_by(Account.id.asc())
        .first()
    )
    account_id = account.id if account else None

    # 1. Solicitud de servicio (RFQ)
    sr = ServiceRequest(
        reference="SOL-0001", account_id=account_id, operation_type="exportacion",
        transport_mode="maritimo", service_type="puerta_puerta", incoterm="FOB",
        origin="Manzanillo, MX", destination="Long Beach, US", cargo_type="Carga general",
        load_type="FCL", container_equipment="1x40'HC", status="cotizada",
        tenant_id=TENANT_ID, company_id=COMPANY_ID,
    )
    db.add(sr)
    db.flush()

    # 2. Cotización aceptada con conceptos
    quote = Quote(
        reference="COT-0001", service_request_id=sr.id, account_id=account_id, currency="USD",
        status="aceptada", tenant_id=TENANT_ID, company_id=COMPANY_ID,
    )
    db.add(quote)
    db.flush()
    items = [
        ("flete_internacional", 1, 1800, 2200),
        ("transporte_terrestre", 1, 350, 500),
        ("despacho_aduanal", 1, 200, 320),
    ]
    total_cost = total_sale = 0
    for concept, qty, cost, sale in items:
        db.add(QuoteItem(quote_id=quote.id, concept=concept, quantity=qty, unit_cost=cost,
                         unit_sale=sale, currency="USD", tenant_id=TENANT_ID, company_id=COMPANY_ID))
        total_cost += qty * cost
        total_sale += qty * sale
    quote.total_cost = total_cost
    quote.total_sale = total_sale
    sr.status = "liberada"

    # 3. Embarque liberado a Operaciones
    shipment = Shipment(
        reference="EMB-0001", quote_id=quote.id, service_request_id=sr.id, account_id=account_id,
        operation_type=sr.operation_type, transport_mode=sr.transport_mode, service_type=sr.service_type,
        incoterm=sr.incoterm, origin=sr.origin, destination=sr.destination,
        status="booking", booking_number="BKG-778812", tenant_id=TENANT_ID, company_id=COMPANY_ID,
    )
    db.add(shipment)
    db.flush()
    db.add(ShipmentDocument(shipment_id=shipment.id, doc_kind="master", doc_type="MBL",
                            number="MBLU12345678", tenant_id=TENANT_ID, company_id=COMPANY_ID))

    db.commit()
    print("✓ Flujo comercial: 1 solicitud, 1 cotización aceptada (3 conceptos), 1 embarque + documento MBL")


def seed_invoicing_and_events(db) -> None:
    """Factura desde el embarque (con pago parcial) + hitos de la bitácora."""
    from decimal import Decimal

    if db.query(Invoice).filter(
        Invoice.tenant_id == TENANT_ID, Invoice.company_id == COMPANY_ID, Invoice.deleted_at.is_(None)
    ).first():
        print("• Ya existe facturación; se omite")
        return

    shipment = (
        db.query(Shipment)
        .filter(Shipment.tenant_id == TENANT_ID, Shipment.company_id == COMPANY_ID, Shipment.deleted_at.is_(None))
        .order_by(Shipment.id.asc())
        .first()
    )
    if not shipment:
        print("• Sin embarque; se omite facturación")
        return

    # Bitácora de hitos (según tipo de operación, incluye puntos de decisión)
    events = shipments_service.seed_default_milestones(db, shipment.id, TENANT_ID, COMPANY_ID)

    # Resolver los puntos de decisión como autorizados y completar los hitos simples,
    # para poder cerrar operativamente el embarque.
    for ev in events:
        if ev.kind == "decision":
            shipments_service.decide_shipment_event(
                db, ev.id, ShipmentEventDecisionInput(outcome="autorizado"), TENANT_ID, COMPANY_ID
            )
        else:
            shipments_service.complete_shipment_event(db, ev.id, TENANT_ID, COMPANY_ID)

    # Cierre operativo con costos finales (dispara la facturación — R-F-01/R-E-22)
    shipments_service.close_shipment(
        db, shipment.id,
        ShipmentCloseInput(actual_cost_total=Decimal("2350"), cost_currency="USD", notes="Cierre demo"),
        TENANT_ID, COMPANY_ID,
    )

    # Factura generada desde el embarque cerrado (toma conceptos de la cotización)
    invoice = invoices_service.generate_from_shipment(db, shipment.id, TENANT_ID, COMPANY_ID)
    invoices_service.emit_invoice(db, invoice.id, TENANT_ID, COMPANY_ID)
    invoices_service.create_payment(
        db, PaymentCreate(invoice_id=invoice.id, amount=Decimal("1000"), method="transferencia", reference="SPEI-001"),
        TENANT_ID, COMPANY_ID,
    )
    print("✓ Embarque cerrado, factura emitida con pago parcial + bitácora con decisiones resueltas")


# Carriles del proceso (R-T-07): a qué módulos/acciones puede acceder cada rol.
# clave = (code, nombre); valor = función que decide si un permiso pertenece al rol.
def _carril_roles() -> dict:
    # cada función recibe el CÓDIGO del permiso (str) y decide si pertenece al carril
    return {
        ("ventas", "Ventas"): lambda c: c.startswith("crm.") or c in {"ops.access", "ops.shipment.view"},
        ("operaciones", "Operaciones"): lambda c: c.startswith("ops.")
            or c in {"crm.access", "crm.account.view", "crm.quote.view", "crm.service_request.view", "fin.access", "fin.invoice.view"},
        ("facturacion", "Facturación"): lambda c: c.startswith("fin.")
            or c in {"ops.access", "ops.shipment.view", "crm.access", "crm.account.view"},
        ("consulta", "Consulta"): lambda c: c.endswith(".access") or c.endswith(".view"),
    }


def ensure_company(db) -> None:
    """Garantiza la empresa dev (a76.company id=1), requerida por la FK company_id de
    los roles/permisos (core.company_roles → a76.company). La plantilla no la crea."""
    from sqlalchemy import text

    exists = db.execute(text("SELECT 1 FROM a76.company WHERE id = :id"), {"id": COMPANY_ID}).first()
    if exists:
        print(f"• Empresa id={COMPANY_ID} ya existe (a76.company)")
        return
    db.execute(
        text("INSERT INTO a76.company (id, tenant_id) VALUES (:id, :tid)"),
        {"id": COMPANY_ID, "tid": TENANT_ID},
    )
    db.execute(text("SELECT setval('a76.company_id_seq', (SELECT MAX(id) FROM a76.company))"))
    db.commit()
    print(f"✓ Empresa demo id={COMPANY_ID} creada (a76.company)")


def seed_carril_roles(db) -> None:
    """Crea los roles por carril del proceso con sus permisos (R-T-07). Idempotente."""
    # Poblar el catálogo de permisos desde el registry de código
    PermissionService(db).sync_permissions()
    all_perms = db.query(Permission).filter(Permission.is_active == True).all()  # noqa: E712

    created = 0
    for (code, name), belongs in _carril_roles().items():
        role = (
            db.query(CompanyRole)
            .filter(CompanyRole.company_id == COMPANY_ID, CompanyRole.tenant_id == TENANT_ID, CompanyRole.code == code)
            .first()
        )
        if not role:
            role = CompanyRole(
                company_id=COMPANY_ID, tenant_id=TENANT_ID, name=name, code=code,
                description=f"Rol de carril: {name}", is_active=True,
            )
            db.add(role)
            db.flush()
            created += 1
        existing_perm_ids = {
            pid for (pid,) in db.query(RolePermission.permission_id).filter(RolePermission.company_role_id == role.id)
        }
        for perm in all_perms:
            if belongs(perm.code) and perm.id not in existing_perm_ids:
                db.add(RolePermission(
                    company_role_id=role.id, permission_id=perm.id,
                    tenant_id=TENANT_ID, company_id=COMPANY_ID,
                ))
    db.commit()
    print(f"✓ Roles por carril sembrados (nuevos: {created}) — Ventas, Operaciones, Facturación, Consulta")


def main() -> None:
    db = CoreSessionLocal()
    try:
        ensure_tenant(db)
        pipeline, stages = ensure_pipeline(db)
        seed_sample_data(db, pipeline, stages)
        seed_suppliers_and_related(db)
        seed_commercial_and_ops(db)
        seed_invoicing_and_events(db)
        ensure_company(db)
        seed_carril_roles(db)
        print("\nSeed CRM completado.")
    finally:
        db.close()


if __name__ == "__main__":
    main()
