"""
Invoice WINSAAI Service – Legacy Format
Generates the legacy WINSAAI .txt file that matches the output of the
original Clarion system, used with the WINSAAI utility.

Format reference (from real system example):
    501|1|REGIME|PED_NUM|ADUANA|IGI|IVA|DTA|PREVAL|OTROS|WEIGHT|BULTOS|7|7|7||...
    503||GUIA||
    505|FACTURA|YYYYMMDD|INCOTERM|MONEDA|VALOR_ME|VALOR_MN|...|CANT_LINES|SOLD_TO_SHORT|SOLD_TO_NAME|
    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|||...
    558||
    999|
"""

import re
from typing import List, Optional
from sqlalchemy.orm import Session, joinedload

from api.v1.modules.a76.invoices.models import (
    InvoiceHeader, InvoiceComplianceMx, InvoiceFinancials, InvoiceLogistics,
)
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.units_of_measure.models import UnitOfMeasure
from api.v1.modules.a76.parts.models import Part
from api.v1.modules.a76.clients_and_providers.models import ClientProvider
from api.v1.modules.a76.customs_brokers.models import CustomsBroker
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
from api.v1.modules.a76.pedmientos.models.pedimento_contributions import PedimentoContributions


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

def _s(val) -> str:
    """Safe string conversion – return '' for None."""
    if val is None:
        return ""
    return str(val).strip()


def _n(val, decimals: int = 0) -> str:
    """Format numeric value without leading zeros, with optional decimal places."""
    if val is None:
        return "0"
    try:
        f = float(val)
        if decimals:
            return f"{f:.{decimals}f}"
        else:
            # Integer representation, no decimals
            return str(int(f))
    except (TypeError, ValueError):
        return "0"


def _fmt_date_ymd(dt) -> str:
    """Format as YYYYMMDD (legacy WINSAAI style)."""
    if not dt:
        return ""
    return dt.strftime("%Y%m%d")


def _clean(text: str) -> str:
    """Remove pipes and line breaks that would corrupt the pipe-delimited format."""
    if not text:
        return ""
    text = re.sub(r'[\r\n]+', ' ', text)
    text = text.replace('|', '/')
    return text.strip()


def _sanitize_filename(name: str) -> str:
    return re.sub(r'[/\\:*?"<>|]', '', name)


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

class WinsaaiService:
    def __init__(self, db: Session, company_id: int, tenant_id: int):
        self.db = db
        self.company_id = company_id
        self.tenant_id = tenant_id

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

    def generate_winsaai_data(self, invoice_ids: List[int], is_temporal: bool) -> List[str]:
        all_lines: List[str] = []
        for inv_id in invoice_ids:
            invoice = (
                self.db.query(InvoiceHeader)
                .options(
                    joinedload(InvoiceHeader.compliance_mx),
                    joinedload(InvoiceHeader.financials),
                    joinedload(InvoiceHeader.logistics),
                    joinedload(InvoiceHeader.details),
                )
                .filter(InvoiceHeader.id == inv_id)
                .first()
            )
            if not invoice:
                continue
            all_lines.extend(self._gen_legacy(invoice))
        # Terminator
        all_lines.append("999|")
        return all_lines

    # ── MAIN GENERATOR ───────────────────────────────────────────────────────

    def _gen_legacy(self, inv: InvoiceHeader) -> List[str]:
        lines: List[str] = []

        comp: Optional[InvoiceComplianceMx] = inv.compliance_mx
        fin: Optional[InvoiceFinancials] = inv.financials
        log: Optional[InvoiceLogistics] = inv.logistics

        # ── Pedimento ────────────────────────────────────────────────────────
        ped: Optional[Pedimentos] = None
        if comp and comp.pedimento_id:
            ped = self.db.query(Pedimentos).filter(Pedimentos.id == comp.pedimento_id).first()

        ped_full = (ped.pedimento_number or "") if ped else ""
        # Clarion: SUB(Pedimento,4,4) → chars 3-7 = license/patente
        # SUB(Pedimento,9,7) → after hyphen = 7-digit number
        # Legacy example uses just the number portion (the integer part)
        # We'll parse: aduana(3) + año(2) + patente(4) + num(7) or use raw sub-strings
        ped_aduana = _s(ped.customs_office) if ped else ""
        ped_regime = _s(ped.regime) if ped else ""

        # Try to extract 7-digit number from pedimento_number
        # Formats: "010 IN 3130441" or "01023130441"
        ped_num_raw = "".join(filter(str.isdigit, ped_full))
        if len(ped_num_raw) >= 7:
            ped_num_short = ped_num_raw[-7:]   # last 7 digits (the folio)
        else:
            ped_num_short = ped_num_raw

        # ── Partners ─────────────────────────────────────────────────────────
        sold_to = self._get_partner(comp.sold_to_id) if comp and comp.sold_to_id else None
        provider = self._get_partner(comp.provider_id) if comp and comp.provider_id else None

        # ── Financial totals ─────────────────────────────────────────────────
        tc = float(fin.exchange_rate or 1) if fin else 1.0
        currency = _s(fin.currency_type) if fin else "USD"
        if currency.upper() in ("MXP", "MXN"):
            currency = "MXP"
        else:
            currency = "USD"

        valor_me_hdr = float(fin.value_me or 0) if fin else 0.0
        freight   = float(fin.freight or 0) if fin else 0.0
        insurance = float(fin.insurance or 0) if fin else 0.0
        gross_wt_hdr = float(fin.gross_weight or 0) if fin else 0.0
        bundles   = int(fin.bundle_count or 0) if fin else 0
        incoterm  = _s(log.incoterm) if log else ""

        # ── Items (needed early for summing) ─────────────────────────────────
        items = (
            self.db.query(LineItem)
            .filter(LineItem.invoice_id == inv.id)
            .order_by(LineItem.line_number)
            .all()
        )

        # Sum values from line items when header totals are missing/zero
        sum_val_me  = 0.0
        sum_val_mn  = 0.0
        sum_gross   = 0.0   # goes into 501 (peso bruto total)
        sum_bultos  = 0
        for _item in items:
            _fin = _item.financial
            _qty = _item.quantity
            if _fin:
                sum_val_me += float(_fin.value_usd or 0)
                sum_val_mn += float(_fin.value_mxn or 0)
            if _qty:
                sum_gross  += float(_qty.gross_weight or _qty.net_weight or 0)
                sum_bultos += int(_qty.package_quantity or 0)

        # Use header value if present, otherwise sum from items
        valor_me = valor_me_hdr if valor_me_hdr > 0 else sum_val_me
        valor_mn = (valor_me * tc) if sum_val_mn == 0 else sum_val_mn
        gross_wt = gross_wt_hdr if gross_wt_hdr > 0 else sum_gross
        if bundles == 0 and sum_bultos > 0:
            bundles = sum_bultos

        # ── Contributions (DTA, Prevalidación, IGI, IVA) ─────────────────────
        igi_total   = 0.0
        iva_total   = 0.0
        dta_total   = 0.0
        preval_total = 0.0
        if ped:
            contribs = (
                self.db.query(PedimentoContributions)
                .filter(PedimentoContributions.pedimento_id == ped.id)
                .all()
            )
            for c in contribs:
                code = (c.contribucion or "").upper()
                abrev = (c.abreviacion or "").upper()
                amount = float(c.importe or 0)
                if "IGI" in code or abrev == "IGI":
                    igi_total += amount
                elif "IVA" in code or abrev == "IVA":
                    iva_total += amount
                elif "DTA" in code or abrev == "DTA":
                    dta_total += amount
                elif "PREVAL" in code or "15" in code:
                    preval_total += amount



        # ── REGISTRO 501 ──────────────────────────────────────────────────────
        # Legacy: 501|1|REGIME|PED_NUM|ADUANA|IGI|IVA|DTA|PREVAL|0|GROSS_WT|BUNDLES|7|7|7||...
        # Example: 501|1|IN|3130441|ENTRADA|0|0|0|0|0|4156.960|0|7|7|7||||160|1764|...
        lines.append(
            f"501|1|{ped_regime}|{ped_num_short}|{ped_aduana}|"
            f"{_n(igi_total, 3)}|{_n(iva_total, 3)}|{_n(dta_total, 3)}|{_n(preval_total, 3)}|0|"
            f"{_n(gross_wt, 3)}|{bundles}|7|7|7||||"
            f"{_n(freight, 3)}|{_n(insurance, 3)}|1|||||||||||||||"
        )

        # ── REGISTRO 503 ──────────────────────────────────────────────────────
        niu = _s(comp.niu_number) if comp else ""
        guide = _s(comp.guide_type_to_identify) if comp else ""
        lines.append(f"503|{niu}|{guide}||")

        # ── REGISTRO 505 ──────────────────────────────────────────────────────
        # Legacy: 505|FACTURA|YYYYMMDD|INCOTERM|MONEDA|VALOR_ME|VALOR_MN||||||||||CANT_LINES|SOLD_SHORT|SOLD_NAME|
        # Example: 505|1111|20230525|DAP|USD|69760.00|69760.00||||||||||3|ENTRADA|ENTRADA GROUP...|
        sold_short = _s(sold_to.short_name) if sold_to else ""
        sold_name  = _clean(_s(sold_to.name)) if sold_to else ""
        invoicenum = _s(inv.invoice_number)
        fecha_fac  = _fmt_date_ymd(inv.invoice_date)

        lines.append(
            f"505|{invoicenum}|{fecha_fac}|{incoterm}|{currency}|"
            f"{_n(valor_me, 2)}|{_n(valor_mn, 2)}||||||||||"
            f"{len(items)}|{sold_short}|{sold_name}|"
        )

        # ── REGISTROS 551 + 558 por partida ──────────────────────────────────
        # Legacy 551 format (from example):
        # 551|FRACCION|DESC|VAL_MN(int)|VAL_ME(dec)|CANT(3dec)|UM_ADUANA|CANT_TAR(3dec)|0|0|FPAGO|||P_ORIG|P_IMP|||||0||PESO|||MONEDA|FACTURA||0|PESO|PESO|MET_VALOR|LINEA|||||||FPAGO||||||||99|
        linea_num = 0
        for item in items:
            linea_num += 1
            item_qty:  Optional[LineQuantity]   = item.quantity
            item_fin:  Optional[LineFinancial]  = item.financial
            item_cust: Optional[LineCustom]     = item.customs
            item_desc: Optional[LineDescription] = item.description

            # Fraccion
            fraccion = ""
            if item_cust and item_cust.fraction:
                fraccion = item_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()
            desc = ""
            if part:
                desc = _clean(part.description_spanish or part.description or "")
            if not desc and item_desc:
                desc = _clean(item_desc.description_spanish or item_desc.description1 or "")

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

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

            # UM aduanas (Clave_AMex)
            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)

            # Cantidad tarifa
            cant_tar = float(item_qty.quantity_uma or cant) if item_qty else cant

            # Forma de pago (del item)
            forma_pago = _s(item.payment_method)

            # País origen y país importador
            pais_orig = _s(item_cust.origin_country) if item_cust else ""
            pais_imp = ""
            if sold_to and sold_to.address:
                pais_imp = _s(sold_to.address.country)

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

            # MetValor
            met_valor = _s(item.valuation_method) or _s(comp.value_method if comp else "")

            # Legacy 551 field layout (counting from example):
            # 551|frac|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|||||||fpago||||||||99|
            lines.append(
                f"551|{fraccion}|{desc}|{_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}|{linea_num}|||||||{forma_pago}||||||||99|"
            )

            # ── REGISTRO 558 ──────────────────────────────────────────────────
            # Legacy: always emit an empty 558 after each 551
            # If there is additional info, emit it, otherwise just empty: 558||
            info = ""
            if item_desc and hasattr(item_desc, 'extra_description'):
                info = _clean(item_desc.extra_description or "")
            lines.append(f"558|{info}|")

        return lines

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

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

    def get_filename(self, invoice: InvoiceHeader) -> str:
        inv_num = _sanitize_filename(invoice.invoice_number or "")
        return f"{inv_num}.txt"
