from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path

from app.excel_import.config import (
    SHEET_CAVES,
    SHEET_EAU_CHAUDE,
    SHEET_EAU_FROIDE,
    SHEET_INFO,
    SHEET_SURFACES,
    SYNDIC_LOT_COPRO_OVERRIDE,
)
from app.excel_import.parsers import (
    _clean,
    normalize_lot_copro,
    split_lot_copro_parts,
    split_milliemes,
    split_range,
)


def _require_openpyxl():
    try:
        import openpyxl
    except ImportError as exc:
        raise SystemExit("openpyxl requis : pip install openpyxl") from exc
    return openpyxl


@dataclass
class InfoRow:
    row_num: int
    lot_syndic: str
    lot_copro_excel: str
    nom_copro: str
    prenom_copro: str
    etage_porte: str
    bal: str
    cave: str
    milliemes: str
    adresse: str
    tel_copro: str
    mail_copro: str
    autres_coordonnees: str
    gestionnaire: str
    civilite_occ: str
    nom_occ: str
    prenom_occ: str
    tel_occ: str
    mail_occ: str
    infos_diverses: str
    anciens_occupants: str

    @classmethod
    def from_row(cls, row_num: int, row: tuple) -> "InfoRow | None":
        lot_syndic = _clean(row[2] if len(row) > 2 else "")
        lot_copro = _clean(row[3] if len(row) > 3 else "")
        if not lot_syndic and not lot_copro:
            return None
        return cls(
            row_num=row_num,
            lot_syndic=lot_syndic,
            lot_copro_excel=lot_copro,
            nom_copro=_clean(row[4] if len(row) > 4 else ""),
            prenom_copro=_clean(row[5] if len(row) > 5 else ""),
            etage_porte=_clean(row[6] if len(row) > 6 else ""),
            bal=_clean(row[7] if len(row) > 7 else ""),
            cave=_clean(row[8] if len(row) > 8 else ""),
            milliemes=_clean(row[9] if len(row) > 9 else ""),
            adresse=_clean(row[10] if len(row) > 10 else ""),
            tel_copro=_clean(row[11] if len(row) > 11 else ""),
            mail_copro=_clean(row[12] if len(row) > 12 else ""),
            autres_coordonnees=_clean(row[13] if len(row) > 13 else ""),
            gestionnaire=_clean(row[14] if len(row) > 14 else ""),
            civilite_occ=_clean(row[17] if len(row) > 17 else ""),
            nom_occ=_clean(row[18] if len(row) > 18 else ""),
            prenom_occ=_clean(row[19] if len(row) > 19 else ""),
            tel_occ=_clean(row[20] if len(row) > 20 else ""),
            mail_occ=_clean(row[21] if len(row) > 21 else ""),
            infos_diverses=_clean(row[22] if len(row) > 22 else ""),
            anciens_occupants=_clean(row[23] if len(row) > 23 else ""),
        )


@dataclass
class LotDraft:
    """Lot après éclatement éventuel."""

    import_key: str
    lot_syndic: str
    lot_copro: str
    etage: str
    porte: str
    bal: str
    cave: str
    milliemes: str
    surface: float | None = None
    notes: str = ""
    source_row: int = 0
    split_index: int = 0
    split_total: int = 1
    proprio_nom: str = ""
    proprio_prenom: str = ""
    raw_row: InfoRow | None = None

    @property
    def etage_porte(self) -> str:
        if self.etage and self.porte:
            return f"{self.etage} {self.porte}".strip()
        return self.etage or self.porte


def _etage_porte_parts(value: str) -> tuple[str, str]:
    v = _clean(value)
    if not v:
        return "", ""
    if " " in v:
        a, b = v.split(" ", 1)
        return a.strip(), b.strip()
    return v, ""


def resolve_lot_copro(syndic: str, excel_copro: str) -> str:
    if syndic in SYNDIC_LOT_COPRO_OVERRIDE:
        return SYNDIC_LOT_COPRO_OVERRIDE[syndic]
    return normalize_lot_copro(excel_copro)


def expand_info_row(row: InfoRow) -> list[LotDraft]:
    copro_parts = split_lot_copro_parts(row.lot_copro_excel)
    etage_parts = split_range(row.etage_porte) if len(copro_parts) > 1 else [row.etage_porte]
    mill_parts = split_milliemes(row.milliemes)
    if len(copro_parts) == 1:
        mill_parts = [row.milliemes]

    count = max(len(copro_parts), len(etage_parts), len(mill_parts), 1)
    if len(copro_parts) == 1 and count > 1:
        base = resolve_lot_copro(row.lot_syndic, row.lot_copro_excel)
        copro_parts = [f"{base}-{i + 1}" for i in range(count)]

    while len(etage_parts) < count:
        etage_parts.append(etage_parts[-1] if etage_parts else "")
    while len(mill_parts) < count:
        mill_parts.append(mill_parts[-1] if mill_parts else "")
    while len(copro_parts) < count:
        copro_parts.append(copro_parts[-1])

    notes_base = ""
    if row.anciens_occupants:
        notes_base = f"Anciens occupants : {row.anciens_occupants}"
    if row.infos_diverses:
        notes_base = (notes_base + "\n" + row.infos_diverses).strip()

    drafts: list[LotDraft] = []
    for i in range(count):
        if count == 1:
            copro = resolve_lot_copro(row.lot_syndic, copro_parts[0])
            import_key = row.lot_syndic or copro
        else:
            copro = normalize_lot_copro(copro_parts[i])
            import_key = f"{row.lot_syndic}#{i + 1}"

        etage, porte = _etage_porte_parts(etage_parts[i])
        cave = "" if row.cave in ("//", "-", "—") else row.cave

        drafts.append(
            LotDraft(
                import_key=import_key,
                lot_syndic=row.lot_syndic,
                lot_copro=copro,
                etage=etage,
                porte=porte,
                bal=row.bal,
                cave=cave,
                milliemes=mill_parts[i] if i < len(mill_parts) else "",
                notes=notes_base,
                source_row=row.row_num,
                split_index=i + 1,
                split_total=count,
                proprio_nom=row.nom_copro,
                proprio_prenom=row.prenom_copro,
                raw_row=row,
            )
        )
    return drafts


@dataclass
class SurfaceRow:
    lot_syndic: str
    lot_copro: str
    surface: float | None


@dataclass
class ReleveRow:
    etage_porte: str
    lot_copro: str
    proprio_nom: str
    index_valeur: float | None
    occupant_nom: str
    type_eau: str


class ExcelSource:
    def __init__(self, path: Path):
        self.path = path
        openpyxl = _require_openpyxl()
        self._wb = openpyxl.load_workbook(path, read_only=True, data_only=True)

    def close(self) -> None:
        self._wb.close()

    def _sheet(self, expected: str):
        for name in self._wb.sheetnames:
            if name.strip() == expected.strip():
                return self._wb[name]
        raise KeyError(f"Onglet introuvable : {expected!r}")

    def read_info_rows(self) -> list[InfoRow]:
        ws = self._sheet(SHEET_INFO)
        rows: list[InfoRow] = []
        for i, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
            parsed = InfoRow.from_row(i, row)
            if parsed:
                rows.append(parsed)
        return rows

    def read_surfaces(self) -> dict[str, float]:
        ws = self._sheet(SHEET_SURFACES)
        out: dict[str, float] = {}
        for row in ws.iter_rows(min_row=2, values_only=True):
            syndic = _clean(row[0] if len(row) > 0 else "")
            copro = _clean(row[1] if len(row) > 1 else "")
            surf = row[6] if len(row) > 6 else None
            if syndic and surf is not None:
                try:
                    out[syndic] = float(surf)
                except (TypeError, ValueError):
                    pass
            elif copro and surf is not None:
                try:
                    out[f"copro:{normalize_lot_copro(copro)}"] = float(surf)
                except (TypeError, ValueError):
                    pass
        return out

    def read_caves(self) -> dict[str, str]:
        ws = self._sheet(SHEET_CAVES)
        out: dict[str, str] = {}
        for row in ws.iter_rows(min_row=2, values_only=True):
            syndic = _clean(row[0] if len(row) > 0 else "")
            cave = _clean(row[5] if len(row) > 5 else "")
            if syndic:
                out[syndic] = "" if cave in ("//",) else cave
        return out

    def _read_releves(self, sheet_name: str, type_eau: str) -> list[ReleveRow]:
        ws = self._sheet(sheet_name)
        rows: list[ReleveRow] = []
        for row in ws.iter_rows(min_row=4, values_only=True):
            etage = _clean(row[0] if len(row) > 0 else "")
            lot_copro = _clean(row[1] if len(row) > 1 else "")
            if not etage and not lot_copro:
                continue
            idx_raw = row[3] if len(row) > 3 else None
            index_valeur = None
            if idx_raw is not None and str(idx_raw).strip() != "":
                try:
                    index_valeur = float(idx_raw)
                except (TypeError, ValueError):
                    pass
            rows.append(
                ReleveRow(
                    etage_porte=etage,
                    lot_copro=normalize_lot_copro(lot_copro.split("(")[0].strip()),
                    proprio_nom=_clean(row[2] if len(row) > 2 else ""),
                    index_valeur=index_valeur,
                    occupant_nom=_clean(row[5] if len(row) > 5 else ""),
                    type_eau=type_eau,
                )
            )
        return rows

    def read_releves_eau(self) -> list[ReleveRow]:
        return self._read_releves(SHEET_EAU_FROIDE, "eau_froide") + self._read_releves(
            SHEET_EAU_CHAUDE, "eau_chaude"
        )

    def expand_all_lots(self) -> list[LotDraft]:
        lots: list[LotDraft] = []
        for row in self.read_info_rows():
            lots.extend(expand_info_row(row))
        return lots
