from __future__ import annotations

import uuid
from datetime import date, datetime
from decimal import Decimal
from typing import NamedTuple

from sqlalchemy.orm import Session

from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
from api.v1.modules.a76.clients_and_providers.models import (
    ClientOrProviderEnum,
    ClientProvider,
    ClientProviderAddress,
)
from api.v1.modules.a76.customs_brokers.models import CustomsBroker
from api.v1.modules.a76.general_catalogs.company.models import Company
from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
from api.v1.modules.a76.invoices.models import (
    Currency,
    InvoiceComplianceMx,
    InvoiceFinancials,
    InvoiceHeader,
    InvoiceLogistics,
    InvoiceStatus,
    OperationType,
)
from api.v1.modules.a76.items.line_customs.models import LineCustom
from api.v1.modules.a76.items.line_descriptions.models import LineDescription
from api.v1.modules.a76.items.line_financials.models import LineFinancial
from api.v1.modules.a76.items.line_quantities.models import LineQuantity
from api.v1.modules.a76.items.models import LineItem
from api.v1.modules.core.tenants.models import Tenant, TenantType
from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType
from api.v1.modules.public.reference_data.pedimento_regimens.models import RegimenPedimento


class EphemeralOrgIds(NamedTuple):
    """Tenant + company creados por test (IDs altos, baja colisión con datos reales / CI)."""

    tenant_id: int
    company_id: int


def allocate_ephemeral_tenant_company_ids(db: Session) -> tuple[int, int]:
    """
    Genera par (tenant_id, company_id) no usados en esta sesión.
    Mismo entero para ambas PKs es válido (tablas distintas).
    """
    for _ in range(64):
        n = uuid.uuid4().int
        tid = 1_500_000_000 + (n % 99_000_000)
        if db.get(Tenant, tid) is not None:
            continue
        if db.get(Company, tid) is not None:
            continue
        return tid, tid
    raise RuntimeError(
        "No se pudo asignar tenant/company efímero para tests (reintentar o revisar datos en BD)."
    )


def ensure_reference_data(db: Session) -> None:
    for key, desc in [
        ("TEM", "IMPORTACION TEMPORAL"),
        ("DEF", "IMPORTACION DEFINITIVA"),
        ("MEX", "COMPRAS MEXICANAS"),
        ("DONAC", "DONACION"),
    ]:
        if db.get(InvoiceType, key) is None:
            operation = "exp" if key == "DONAC" else "imp"
            db.add(
                InvoiceType(
                    key=key,
                    description=desc,
                    note="seed for tests",
                    type="both",
                    operation=operation,
                )
            )

    if db.get(RegimenPedimento, "A1") is None:
        db.add(
            RegimenPedimento(
                code="A1",
                description="Regimen de prueba",
            )
        )
    db.flush()


def ensure_tenant_company(db: Session, tenant_id: int, company_id: int) -> Company:
    tenant = db.get(Tenant, tenant_id)
    if tenant is None:
        tenant = Tenant(
            id=tenant_id,
            name=f"Tenant {tenant_id}",
            slug=f"pytest-{tenant_id}-{uuid.uuid4().hex[:10]}",
            type=TenantType.SHARED,
            keycloak_realm="test",
            is_active=True,
        )
        db.add(tenant)
        # El FK de a76.company → core.tenants exige que el tenant exista en esta transacción
        # antes del INSERT de company; un solo flush al final puede ordenar mal (Tenant híbrido Column+Mapped).
        db.flush()

    company = db.get(Company, company_id)
    if company is None:
        company = Company(
            id=company_id,
            tenant_id=tenant_id,
            name=f"Company {company_id}",
            rfc="TST010101AAA",
            prosec=False,
        )
        db.add(company)
    db.flush()
    return company


def _ensure_client_provider_address(
    db: Session,
    client: ClientProvider,
    *,
    tenant_id: int,
    company_id: int,
    country: str,
) -> None:
    """
    Garantiza que el cliente tenga fila de dirección. Si el ORM no tiene `address`
    cargada pero en BD ya existe (datos previos en CI / DB compartida), reutiliza
    esa fila en lugar de INSERT duplicado (pkey id = client_id).
    """
    if client.address is not None:
        return
    existing = (
        db.query(ClientProviderAddress)
        .filter(ClientProviderAddress.client_id == client.id)
        .first()
    )
    if existing is not None:
        client.address = existing
        return
    client.address = ClientProviderAddress(
        id=client.id,
        client_id=client.id,
        tenant_id=tenant_id,
        company_id=company_id,
        country=country,
    )


def create_business_catalogs(db: Session, tenant_id: int, company_id: int) -> dict:
    provider = (
        db.query(ClientProvider)
        .filter(
            ClientProvider.tenant_id == tenant_id,
            ClientProvider.company_id == company_id,
            ClientProvider.name == "Proveedor Test",
        )
        .first()
    )
    if provider is None:
        provider = ClientProvider(
            tenant_id=tenant_id,
            company_id=company_id,
            name="Proveedor Test",
            client_or_provider=ClientOrProviderEnum.BOTH,
            is_active=True,
        )
        db.add(provider)
        db.flush()
    _ensure_client_provider_address(
        db,
        provider,
        tenant_id=tenant_id,
        company_id=company_id,
        country="MEX",
    )

    sold_to = (
        db.query(ClientProvider)
        .filter(
            ClientProvider.tenant_id == tenant_id,
            ClientProvider.company_id == company_id,
            ClientProvider.name == "Cliente Test",
        )
        .first()
    )
    if sold_to is None:
        sold_to = ClientProvider(
            tenant_id=tenant_id,
            company_id=company_id,
            name="Cliente Test",
            client_or_provider=ClientOrProviderEnum.BOTH,
            is_active=True,
        )
        db.add(sold_to)
        db.flush()
    _ensure_client_provider_address(
        db,
        sold_to,
        tenant_id=tenant_id,
        company_id=company_id,
        country="USA",
    )

    shipped_to = (
        db.query(ClientProvider)
        .filter(
            ClientProvider.tenant_id == tenant_id,
            ClientProvider.company_id == company_id,
            ClientProvider.name == "Destinatario Test",
        )
        .first()
    )
    if shipped_to is None:
        shipped_to = ClientProvider(
            tenant_id=tenant_id,
            company_id=company_id,
            name="Destinatario Test",
            client_or_provider=ClientOrProviderEnum.BOTH,
            is_active=True,
        )
        db.add(shipped_to)
        db.flush()
    _ensure_client_provider_address(
        db,
        shipped_to,
        tenant_id=tenant_id,
        company_id=company_id,
        country="USA",
    )

    broker = (
        db.query(CustomsBroker)
        .filter(
            CustomsBroker.tenant_id == tenant_id,
            CustomsBroker.company_id == company_id,
            CustomsBroker.broker_key == "A1234",
        )
        .first()
    )
    if broker is None:
        broker = CustomsBroker(
            tenant_id=tenant_id,
            company_id=company_id,
            broker_key="A1234",
            name="Agente Test",
        )
        db.add(broker)
        db.flush()

    uom = (
        db.query(UnitOfMeasure)
        .filter(
            UnitOfMeasure.tenant_id == tenant_id,
            UnitOfMeasure.company_id == company_id,
            UnitOfMeasure.code == "PZA",
        )
        .first()
    )
    if uom is None:
        uom = UnitOfMeasure(
            tenant_id=tenant_id,
            company_id=company_id,
            code="PZA",
            description="Pieza",
        )
        db.add(uom)
        db.flush()

    exr = (
        db.query(ExchangeRate)
        .filter(
            ExchangeRate.tenant_id == tenant_id,
            ExchangeRate.company_id == company_id,
            ExchangeRate.date == datetime(2026, 3, 17, 0, 0, 0),
        )
        .first()
    )
    if exr is None:
        exr = ExchangeRate(
            tenant_id=tenant_id,
            company_id=company_id,
            date=datetime(2026, 3, 17, 0, 0, 0),
            value=Decimal("17.250000"),
            local_currency="MN",
            foreign_currency="ME",
        )
        db.add(exr)

    db.flush()
    return {
        "provider_id": provider.id,
        "sold_to_id": sold_to.id,
        "shipped_to_id": shipped_to.id,
        "broker_id": broker.id,
        "uom_id": uom.id,
    }


def create_import_invoice_with_line(
    db: Session,
    tenant_id: int,
    company_id: int,
    catalogs: dict,
    invoice_type: str = "TEM",
    invoice_number: str = "IMP-0001",
    qty: Decimal = Decimal("10"),
) -> tuple[InvoiceHeader, LineItem]:
    invoice = InvoiceHeader(
        tenant_id=tenant_id,
        company_id=company_id,
        system="fixed_asset",
        operation_type=OperationType.IMP,
        invoice_type=invoice_type,
        document_type="A1",
        invoice_number=invoice_number,
        invoice_date=date(2026, 3, 17),
        status=InvoiceStatus.PENDING,
    )
    invoice.compliance_mx = InvoiceComplianceMx(
        tenant_id=tenant_id,
        company_id=company_id,
        provider_id=catalogs["provider_id"],
        sold_to_id=catalogs["sold_to_id"],
        shipped_to_id=catalogs["shipped_to_id"],
        customs_broker_id=catalogs["broker_id"],
        is_pedimento_pending=True,
        was_reviewed_by_company=True,
    )
    invoice.financials = InvoiceFinancials(
        tenant_id=tenant_id,
        company_id=company_id,
        currency=Currency.FOREIGN,
        exchange_rate=Decimal("17.250000"),
        iva_factor="16",
    )
    invoice.logistics = InvoiceLogistics(
        tenant_id=tenant_id,
        company_id=company_id,
        weight_type="kgs",
    )
    db.add(invoice)
    db.flush()

    line = LineItem(
        tenant_id=tenant_id,
        company_id=company_id,
        invoice_id=invoice.id,
        line_number=1,
        unit_of_measure=catalogs["uom_id"],
    )
    line.quantity = LineQuantity(
        quantity=qty,
        net_weight=Decimal("50"),
        gross_weight=Decimal("55"),
        package_quantity=1,
    )
    line.financial = LineFinancial(
        unit_cost_capture=Decimal("10"),
        value_usd=Decimal("100"),
        value_mxn=Decimal("1725"),
    )
    line.customs = LineCustom(origin_country="MEX", fraction_type="GENERAL")
    line.description = LineDescription(has_serial=False)
    db.add(line)
    db.flush()
    return invoice, line


def create_export_invoice_with_line(
    db: Session,
    tenant_id: int,
    company_id: int,
    catalogs: dict,
    source_import_invoice: InvoiceHeader,
    source_import_line: LineItem,
    qty: Decimal = Decimal("4"),
) -> tuple[InvoiceHeader, LineItem]:
    invoice = InvoiceHeader(
        tenant_id=tenant_id,
        company_id=company_id,
        system="fixed_asset",
        operation_type=OperationType.EXP,
        invoice_type="DONAC",
        document_type="A1",
        invoice_number="EXP-0001",
        invoice_date=date(2026, 3, 18),
        status=InvoiceStatus.PENDING,
    )
    invoice.compliance_mx = InvoiceComplianceMx(
        tenant_id=tenant_id,
        company_id=company_id,
        provider_id=catalogs["provider_id"],
        sold_to_id=catalogs["sold_to_id"],
        shipped_to_id=catalogs["shipped_to_id"],
        customs_broker_id=catalogs["broker_id"],
        is_pedimento_pending=True,
        was_reviewed_by_company=True,
    )
    invoice.financials = InvoiceFinancials(
        tenant_id=tenant_id,
        company_id=company_id,
        currency=Currency.FOREIGN,
        exchange_rate=Decimal("17.250000"),
        iva_factor="16",
    )
    invoice.logistics = InvoiceLogistics(
        tenant_id=tenant_id,
        company_id=company_id,
        weight_type="kgs",
    )
    db.add(invoice)
    db.flush()

    line = LineItem(
        tenant_id=tenant_id,
        company_id=company_id,
        invoice_id=invoice.id,
        line_number=1,
        unit_of_measure=catalogs["uom_id"],
    )
    line.quantity = LineQuantity(
        quantity=qty,
        net_weight=Decimal("20"),
        gross_weight=Decimal("22"),
        package_quantity=1,
    )
    line.financial = LineFinancial(
        unit_cost_capture=Decimal("12"),
        value_usd=Decimal("48"),
        value_mxn=Decimal("828"),
    )
    line.customs = LineCustom(origin_country="MEX", origin_procedure="TEM", fraction_type="GENERAL")
    line.description = LineDescription(has_serial=False)
    db.add(line)
    db.flush()

    db.add(
        FaLineItem(
            id=line.id,
            tenant_id=tenant_id,
            company_id=company_id,
            discharge=True,
            search_invoice=source_import_invoice.invoice_number,
            search_line=source_import_line.line_number,
            is_subitem=False,
        )
    )
    db.flush()
    return invoice, line
