"""
REVISA_SERIES_OTRAS_PAR
Verifies that no export serie marked for discharge is already assigned for
discharge on a different line of the same export invoice.

Clarion mapping
---------------
QueueSeries  → ``seen_series: set[tuple]`` passed in from the caller.
               The set accumulates across all discharge lines so that a serie
               registered on line 1 is detected as duplicate when line 2 is
               processed.

SerExpo (QSeriesExpo) → Serie  (line_item_id = export LineItem.id)
SerImp  (QSeriesImpo) → Serie  (line_item_id = import LineItem.id, TEM)
SerDef  (QSeriesDef)  → Serie  (line_item_id = import LineItem.id, DEF)

Key tuple (equivalent to QueueSeries record used for GET/ADD):
  (export_invoice_number, import_line, serial_number,
   origin_procedure, serie_row, import_invoice_number)

Logic
-----
For each export serie with discharge=True on this line:
  1. Build the key tuple.
  2. Resolve ``serie_row`` if blank:
       TEM → look up the matching row in QSeriesImpo by serial_number
       DEF → look up the matching row in QSeriesDef by serial_number
  3. If the key is already in ``seen_series``  → duplicate error.
  4. Otherwise → add to ``seen_series`` (mark as seen for subsequent lines).
"""

from typing import Optional, Set, Tuple

from sqlalchemy import select
from sqlalchemy.orm import Session

from api.v1.modules.a76.invoices.models import InvoiceHeader
from api.v1.modules.a76.items.models import LineItem
from api.v1.modules.a76.items.series.models import Serie
from api.v1.modules.a76.invoices.exports.process.sub_process.discharge_types import (
    DownloadEntry,
)
from core.exceptions import ErrorCollector

# Type alias for the duplicate-detection key (equiv. to QueueSeries record)
_SeriesKey = Tuple[str, int, str, str, Optional[int], str]


def review_series_other_lines(
    db: Session,
    invoice: InvoiceHeader,
    line: LineItem,
    entry: DownloadEntry,
    seen_series: Set[_SeriesKey],
    errors: ErrorCollector,
) -> None:
    """
    REVISA_SERIES_OTRAS_PAR
    Checks that no export serie on ``line`` (with discharge=True) is already
    registered for discharge on another line of the same invoice.

    Parameters
    ----------
    db          : active SQLAlchemy session
    invoice     : the export invoice being processed
    line        : the export LineItem whose series are being validated
    entry       : the DownloadEntry for this line
    seen_series : mutable set shared across all calls within one invoice
                  processing run — accumulates keys as lines are processed
    errors      : shared error collector
    """
    export_series = (
        db.execute(
            select(Serie).where(
                Serie.line_item_id == line.id,
                Serie.discharge == True,  # noqa: E712
            )
        )
        .scalars()
        .all()
    )

    if not export_series:
        return

    # Resolve the import line id once (needed for serie row lookup)
    import_line_id = _resolve_import_line_id(db, invoice, entry)

    for serie in export_series:
        serial = serie.serial_numbers or ""
        import_serie_row = serie.serie_row

        # If import_serie_row is not set on the export serie, resolve it from
        # the import series table by matching serial_number
        if import_serie_row is None and import_line_id is not None:
            import_serie_row = _resolve_import_serie_row(
                db, import_line_id, serial
            )

        key: _SeriesKey = (
            invoice.invoice_number or "",
            entry.import_line,
            serial,
            entry.origin_procedure,
            import_serie_row,
            entry.import_invoice,
        )

        if key in seen_series:
            # Find which export line already claimed this serie
            existing_line = _find_existing_export_line(
                db, invoice, line.id, serial, entry
            )
            errors.add_error(
                field=f"line[{entry.export_line}].series[{serie.row}]",
                message=(
                    f"La Serie: '{serial}' ya fue descargada y está capturada "
                    f"para ser Descargada en la Partida: {existing_line}."
                ),
                solution=[
                    "Capturar otro número de Serie o capturar el Renglón "
                    "de la Serie de Importación."
                ],
                code="SERIE_DUPLICATE_DISCHARGE",
                value={
                    "serial": serial,
                    "export_line": entry.export_line,
                    "conflicting_line": existing_line,
                },
            )
        else:
            seen_series.add(key)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _resolve_import_line_id(
    db: Session,
    invoice: InvoiceHeader,
    entry: DownloadEntry,
) -> Optional[int]:
    """Returns the import LineItem.id for the invoice/line referenced by the entry."""
    from api.v1.modules.a76.invoices.models import InvoiceHeader as IH
    from api.v1.modules.a76.items.models import LineItem as LI

    return db.execute(
        select(LI.id)
        .join(IH, LI.invoice_id == IH.id)
        .where(
            IH.tenant_id == invoice.tenant_id,
            IH.invoice_number == entry.import_invoice,
            LI.line_number == entry.import_line,
        )
    ).scalar_one_or_none()


def _resolve_import_serie_row(
    db: Session,
    import_line_id: int,
    serial_number: str,
) -> Optional[int]:
    """
    Looks up the ``row`` of an import serie by serial_number on the given
    import line — equivalent to the SQL3 query in the Clarion for both TEM
    and DEF cases (both use the same Serie model now).
    """
    return db.execute(
        select(Serie.row).where(
            Serie.line_item_id == import_line_id,
            Serie.serial_numbers == serial_number,
        )
    ).scalar_one_or_none()


def _find_existing_export_line(
    db: Session,
    invoice: InvoiceHeader,
    current_line_id: int,
    serial_number: str,
    entry: DownloadEntry,
) -> int:
    """
    Returns the export_line number of another line on the same invoice that
    already has this serial registered for discharge.
    Falls back to entry.export_line if not found (shouldn't happen in practice).
    """
    from api.v1.modules.a76.items.models import LineItem as LI

    # Find all export lines on this invoice that are not the current one
    other_line_ids = db.execute(
        select(LI.id, LI.line_number).where(
            LI.invoice_id == invoice.id,
            LI.id != current_line_id,
        )
    ).all()

    for row in other_line_ids:
        match = db.execute(
            select(Serie.id).where(
                Serie.line_item_id == row.id,
                Serie.serial_numbers == serial_number,
                Serie.discharge == True,  # noqa: E712
            )
        ).scalar_one_or_none()
        if match is not None:
            return row.line_number

    return entry.export_line
