"""
Pedimentos WINSAAI Service – Legacy Format
Generates the legacy WINSAAI .txt file for a pedimento, consolidating
all linked invoices and their line items into a single file.

Format:
    501|<consolidated header>
    503|<guides per invoice>
    505|<invoice header per invoice>
    551|<item per invoice> (repeated)
    558|<observations per item>
    999|
"""

import re
from typing import List, Optional
from decimal import Decimal
from sqlalchemy.orm import Session, joinedload
from fastapi import HTTPException

from api.v1.modules.a76.invoices.models import (
    InvoiceHeader, InvoiceComplianceMx, InvoiceFinancials, InvoiceLogistics
)
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
from api.v1.modules.a76.items.models import LineItem
from api.v1.modules.a76.items.line_quantities.models import LineQuantity
from api.v1.modules.a76.items.line_financials.models import LineFinancial
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.general_catalogs.company.models import Company
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
from api.v1.modules.a76.customs_brokers.models import CustomsBroker
from api.v1.modules.a76.clients_and_providers.models import ClientProvider
from api.v1.modules.a76.parts.models import Part
from api.v1.modules.a76.classes.models import Class
from .schemas import ErrorValidacion


# ─────────────────────────────────────────────────────────────────────────────
# HELPERS
# ─────────────────────────────────────────────────────────────────────────────

def _s(val) -> str:
    if val is None:
        return ""
    return str(val).strip()


def _n(val, decimals: int = 0) -> str:
    if val is None:
        return "0"
    try:
        f = float(val)
        if decimals:
            return f"{f:.{decimals}f}"
        return str(int(f))
    except (TypeError, ValueError):
        return "0"


def _fmt_date_ymd(dt) -> str:
    if not dt:
        return ""
    return dt.strftime("%Y%m%d")


def _clean(text: str) -> str:
    if not text:
        return ""
    text = re.sub(r'[\r\n]+', ' ', text)
    text = text.replace('|', '/')
    return text.strip()


# ─────────────────────────────────────────────────────────────────────────────
# SERVICE
# ─────────────────────────────────────────────────────────────────────────────

class PedimentosWinsaaiService:
    def __init__(self, db: Session):
        self.db = db
        self.validation_errors: List[ErrorValidacion] = []
        self.line_count = 0

    # ── ENTRY POINT ──────────────────────────────────────────────────────────

    def generate_pedimentos_data(
        self,
        pedimento_ids: List[int],
        is_temporal: bool = True,
        is_by_class: bool = False,
    ) -> List[str]:
        lines: List[str] = []
        self.line_count = 0
        self.validation_errors = []

        company = self.db.query(Company).first()
        if not company:
            raise HTTPException(status_code=404, detail="Company information not found")

        for p_id in pedimento_ids:
            pedimento = self.db.query(Pedimentos).filter(Pedimentos.id == p_id).first()
            if not pedimento:
                continue

            # All invoices linked to this pedimento
            invoices = (
                self.db.query(InvoiceHeader)
                .join(InvoiceHeader.compliance_mx)
                .filter(InvoiceComplianceMx.pedimento_id == p_id)
                .options(
                    joinedload(InvoiceHeader.compliance_mx),
                    joinedload(InvoiceHeader.financials),
                    joinedload(InvoiceHeader.logistics),
                )
                .all()
            )

            if not invoices:
                self.validation_errors.append(ErrorValidacion(
                    linea=self.line_count,
                    descripcion=f"Pedimento {pedimento.pedimento_number} no tiene facturas vinculadas",
                    identificador=pedimento.pedimento_number,
                ))
                continue

            # ── Consolidate totals across all invoices for 501 ────────────────
            total_peso  = 0.0
            total_bultos = 0
            total_flete  = 0.0
            total_seguro = 0.0
            total_embalaje = 0.0
            total_otros  = 0.0

            for inv in invoices:
                fin = inv.financials
                if fin:
                    total_flete    += float(fin.freight or 0)
                    total_seguro   += float(fin.insurance or 0)
                    total_embalaje += float(getattr(fin, 'packaging', None) or 0)
                    total_otros    += float(getattr(fin, 'other_increments', None) or 0)

                    h_peso = float(fin.gross_weight or 0)
                    h_bultos = int(fin.bundle_count or 0)
                    if h_peso > 0:
                        total_peso += h_peso
                    else:
                        rows = (
                            self.db.query(LineQuantity.gross_weight)
                            .join(LineItem)
                            .filter(LineItem.invoice_id == inv.id)
                            .all()
                        )
                        total_peso += sum(float(r[0] or 0) for r in rows)

                    if h_bultos > 0:
                        total_bultos += h_bultos
                    else:
                        rows = (
                            self.db.query(LineQuantity.package_quantity)
                            .join(LineItem)
                            .filter(LineItem.invoice_id == inv.id)
                            .all()
                        )
                        total_bultos += sum(int(r[0] or 0) for r in rows)

            # ── REGISTRO 501 (once per pedimento) ────────────────────────────
            lines.append(self._record_501(
                pedimento, invoices[0],
                total_peso, total_bultos,
                total_flete, total_seguro, total_embalaje, total_otros,
            ))

            # ── One 503 + 505 + 551s per invoice ─────────────────────────────
            for inv_idx, inv in enumerate(invoices, 1):
                # 503
                lines.append(self._record_503(inv))

                # Sum financial totals for 505
                items = (
                    self.db.query(LineItem)
                    .options(
                        joinedload(LineItem.financial),
                        joinedload(LineItem.quantity),
                        joinedload(LineItem.customs),
                        joinedload(LineItem.description),
                        joinedload(LineItem.part_info),
                        joinedload(LineItem.unit_of_measure_info),
                        joinedload(LineItem.class_info),
                        joinedload(LineItem.fa_data),
                    )
                    .filter(LineItem.invoice_id == inv.id)
                    .all()
                )

                sum_me = sum(float(it.financial.value_usd or 0) for it in items if it.financial)
                sum_mn = sum(float(it.financial.value_mxn or 0) for it in items if it.financial)
                # Fallback to header
                if inv.financials:
                    hdr_me = float(inv.financials.value_me or 0)
                    if hdr_me > 0:
                        sum_me = hdr_me

                # 505
                lines.append(self._record_505(inv, sum_mn, sum_me, inv_idx))

                # Items — optionally grouped by class
                items_to_process = self._group_items(items, is_by_class)

                for it_idx, (item, source_items) in enumerate(items_to_process, 1):
                    tc = float(inv.financials.exchange_rate or 1) if inv.financials else 1.0
                    lines.append(self._record_551(item, inv, tc, it_idx))

                    # 554 identifiers
                    cust = item.customs
                    if cust and cust.fraction_type:
                        ftype = cust.fraction_type
                        if ftype == "TLCS":
                            lines.append(f"554|TL|{_s(cust.origin_country)}|")
                        elif ftype == "PROSEC":
                            lines.append(f"554|PS|{_s(cust.sector)}|")
                        elif ftype == "ALADI":
                            lines.append(f"554|AL|{_s(cust.sector)}|")

                    # 558 observations
                    parts: List[str] = []
                    if item.description:
                        if item.description.brand:
                            parts.append(f"MARCA: {item.description.brand}")
                        if item.description.model:
                            parts.append(f"MODELO: {item.description.model}")
                        if item.description.extra_description:
                            parts.append(item.description.extra_description)
                    for src in source_items:
                        if src.fa_data and src.fa_data.equipment_message:
                            if src.fa_data.equipment_message not in parts:
                                parts.append(src.fa_data.equipment_message)

                    full_obs = _clean(". ".join(parts))
                    if full_obs:
                        for i in range(0, len(full_obs), 120):
                            lines.append(f"558|{full_obs[i:i+120]}|")
                    else:
                        lines.append("558||")

                self.line_count += len(items_to_process)

        lines.append("999|")
        return lines

    # ── RECORD BUILDERS ──────────────────────────────────────────────────────

    def _record_501(
        self,
        ped: Pedimentos,
        rep_inv: InvoiceHeader,
        peso: float, bultos: int,
        flete: float, seguro: float, embalaje: float, otros: float,
    ) -> str:
        """
        Legacy: 501|1|REGIME|NUM7|ADUANA|IGI|IVA|DTA|PREVAL|0|PESO|BULTOS|7|7|7||||FLETE|SEGURO|...
        Example: 501|1|IN|3130441|ENTRADA|0|0|0|0|0|4156.960|0|7|7|7||||160|1764|...
        """
        regime   = _s(ped.regime)
        aduana   = _s(ped.customs_office)
        num_raw  = "".join(filter(str.isdigit, _s(ped.pedimento_number)))
        num7     = num_raw[-7:] if len(num_raw) >= 7 else num_raw

        return (
            f"501|1|{regime}|{num7}|{aduana}|0|0|0|0|0|"
            f"{_n(peso, 3)}|{bultos}|7|7|7||||"
            f"{_n(flete, 3)}|{_n(seguro, 3)}|1|||||||||||||||||"
        )

    def _record_503(self, inv: InvoiceHeader) -> str:
        comp = inv.compliance_mx
        niu   = _s(comp.niu_number) if comp else ""
        guide = _s(comp.guide_type_to_identify) if comp else ""
        return f"503|{niu}|{guide}||"

    def _record_505(
        self,
        inv: InvoiceHeader,
        val_mn: float,
        val_me: float,
        seq: int,
    ) -> str:
        """
        Legacy: 505|FACTURA|YYYYMMDD|INCOTERM|MONEDA|VAL_ME|VAL_MN|...|COUNT|SHORT|NAME|
        """
        comp    = inv.compliance_mx
        fin     = inv.financials
        log     = inv.logistics

        invoicenum = _s(inv.invoice_number)
        fecha      = _fmt_date_ymd(inv.invoice_date)
        incoterm   = _s(log.incoterm) if log else ""
        currency   = _s(fin.currency_type) if fin else "USD"
        currency   = "MXP" if currency.upper() in ("MXP", "MXN") else "USD"
        tc         = float(fin.exchange_rate or 1) if fin else 1.0

        # val_mn fallback
        if val_mn == 0 and val_me > 0:
            val_mn = val_me * tc

        sold_to_id = comp.sold_to_id if comp else None
        sold_to = self._get_partner(sold_to_id)
        sold_short = _s(sold_to.short_name) if sold_to else ""
        sold_name  = _clean(_s(sold_to.name)) if sold_to else ""

        # Count items for this invoice
        item_count = (
            self.db.query(LineItem)
            .filter(LineItem.invoice_id == inv.id)
            .count()
        )

        return (
            f"505|{invoicenum}|{fecha}|{incoterm}|{currency}|"
            f"{_n(val_me, 2)}|{_n(val_mn, 2)}||||||||||"
            f"{item_count}|{sold_short}|{sold_name}|"
        )

    def _record_551(
        self,
        item: LineItem,
        inv: InvoiceHeader,
        tc: float,
        line_num: int,
    ) -> str:
        """
        Legacy 551:
        551|FRACCION|DESC|VAL_MN|VAL_ME|CANT|UM|CANT_TAR|0|0|FPAGO|||P_ORIG|P_IMP|||||0||PESO|||MONEDA|FACTURA||0|PESO|PESO|MET_VALOR|LINEA|||...99|
        """
        fin  = item.financial
        qty  = item.quantity
        cust = item.customs
        desc = item.description

        # Fracción
        fraccion = ""
        if cust and cust.fraction:
            fraccion = cust.fraction[:8]

        # Descripción
        part = None
        if item.part_number_id:
            part = self.db.query(Part).filter(Part.id == item.part_number_id).first()
        description = ""
        if part:
            description = _clean(part.description_spanish or part.description or "")
        if not description and desc:
            description = _clean(getattr(desc, 'description_spanish', None) or getattr(desc, 'description1', None) or "")
        if not description and item.class_info:
            description = _clean(getattr(item.class_info, 'description_es', None) or "")

        # Valores
        val_me = float(fin.value_usd or 0) if fin else 0.0
        val_mn = float(fin.value_mxn or val_me * tc) if fin else val_me * tc

        # Cantidades
        cant = float(qty.quantity or 0) if qty else 0.0

        # UM aduanas
        um_aduana = ""
        if item.unit_of_measure:
            uom = self.db.query(UnitOfMeasure).filter(UnitOfMeasure.id == item.unit_of_measure).first()
            if uom:
                um_aduana = _s(uom.customs_code)

        cant_tar = float(qty.quantity_uma or cant) if qty else cant

        # FormaPago y MetValor
        forma_pago = _s(item.payment_method)
        met_valor  = _s(item.valuation_method)
        if not met_valor and inv.compliance_mx:
            met_valor = _s(inv.compliance_mx.value_method)

        # País origen e importador
        pais_orig = _s(cust.origin_country) if cust else ""
        pais_imp  = ""
        comp = inv.compliance_mx
        if comp and comp.sold_to_id:
            sold_to = self._get_partner(comp.sold_to_id)
            if sold_to and sold_to.address:
                pais_imp = _s(sold_to.address.country)

        # Peso neto
        peso = float(qty.net_weight or 0) if qty else 0.0

        # Currency and invoice number
        fin_obj = inv.financials
        currency = _s(fin_obj.currency_type) if fin_obj else "USD"
        currency = "MXP" if currency.upper() in ("MXP", "MXN") else "USD"
        invoicenum = _s(inv.invoice_number)

        return (
            f"551|{fraccion}|{description}|{_n(val_mn, 0)}|{_n(val_me, 2)}|"
            f"{_n(cant, 3)}|{um_aduana}|{_n(cant_tar, 3)}|0|0|{forma_pago}|||"
            f"{pais_orig}|{pais_imp}|||||0||{_n(peso, 3)}|||"
            f"{currency}|{invoicenum}||0|{_n(peso, 3)}|{_n(peso, 3)}|"
            f"{met_valor}|{line_num}|||||||{forma_pago}||||||||99|"
        )

    # ── HELPERS ──────────────────────────────────────────────────────────────

    def _group_items(self, items, is_by_class: bool):
        """Returns list of (item, source_items). Groups by class if is_by_class=True."""
        if not is_by_class:
            return [(it, [it]) for it in items]

        groups = {}
        for it in items:
            fraction = (it.class_info.fraction if it.class_info else getattr(it.customs, 'fraction', '')) or ''
            country  = getattr(it.customs, 'origin_country', '') or ''
            key = (it.class_id, fraction, country, it.valuation_method)
            if key not in groups:
                groups[key] = {
                    'rep': it,
                    'val_me': 0.0, 'val_mn': 0.0,
                    'qty': 0.0, 'qty_uma': 0.0,
                    'gross': 0.0, 'net': 0.0, 'bultos': 0,
                    'sources': [],
                }
            g = groups[key]
            g['val_me']  += float(it.financial.value_usd or 0) if it.financial else 0
            g['val_mn']  += float(it.financial.value_mxn or 0) if it.financial else 0
            g['qty']     += float(it.quantity.quantity or 0) if it.quantity else 0
            g['qty_uma'] += float(it.quantity.quantity_uma or 0) if it.quantity else 0
            g['gross']   += float(it.quantity.gross_weight or 0) if it.quantity else 0
            g['net']     += float(it.quantity.net_weight or 0) if it.quantity else 0
            g['bultos']  += int(it.quantity.package_quantity or 0) if it.quantity else 0
            g['sources'].append(it)

        result = []
        for g in groups.values():
            rep = g['rep']
            if rep.financial:
                rep.financial.value_usd = g['val_me']
                rep.financial.value_mxn = g['val_mn']
            if rep.quantity:
                rep.quantity.quantity       = g['qty']
                rep.quantity.quantity_uma   = g['qty_uma']
                rep.quantity.gross_weight   = g['gross']
                rep.quantity.net_weight     = g['net']
                rep.quantity.package_quantity = g['bultos']
            if rep.class_info and rep.customs:
                rep.customs.fraction = getattr(rep.class_info, 'fraction', rep.customs.fraction)
            result.append((rep, g['sources']))
        return result

    def _get_partner(self, partner_id: Optional[int]) -> Optional[ClientProvider]:
        if not partner_id:
            return None
        return (
            self.db.query(ClientProvider)
            .options(joinedload(ClientProvider.address))
            .filter(ClientProvider.id == partner_id)
            .first()
        )