"""
create_examples.py - Liest Stuecklisten-Daten aus Excel-Dateien im Examples-Ordner.

Verwendung:
  python create_examples.py [--example-dir <pfad>]
"""

from __future__ import annotations

import argparse
import csv
import json
import logging
import re
import sys
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Any

log = logging.getLogger(__name__)


@dataclass
class StkItem:
    position: str
    teilenummer: str
    bezeichnung: str
    menge: Any


def _cell_str(value: Any) -> str:
    return str(value).strip() if value is not None else ""


def _find_col_index(header_row: tuple[Any, ...], candidates: list[str]) -> int | None:
    normalized = [_cell_str(v).lower() for v in header_row]
    for candidate in candidates:
        candidate_norm = candidate.lower()
        if candidate_norm in normalized:
            return normalized.index(candidate_norm)
    return None


def _extract_stk_liste_ist(xlsx_path: Path) -> list[StkItem]:
    import openpyxl

    wb = openpyxl.load_workbook(str(xlsx_path), read_only=True, data_only=True)
    if "Stk.-Liste IST" not in wb.sheetnames:
        wb.close()
        raise ValueError("Reiter 'Stk.-Liste IST' nicht gefunden")

    ws = wb["Stk.-Liste IST"]

    header_idx: tuple[int | None, int | None, int | None, int | None] | None = None
    items: list[StkItem] = []

    for row in ws.iter_rows(values_only=True):
        if header_idx is None:
            pos_idx = _find_col_index(list(row), ["position", "pos", "pos."])
            part_idx = _find_col_index(
                list(row),
                ["teilenummer", "teilnummer", "article", "artikelnummer"],
            )
            desc_idx = _find_col_index(
                list(row),
                ["bezeichnung", "beschreibung", "description", "benennung"],
            )
            qty_idx = _find_col_index(list(row), ["menge", "anzahl", "qty", "quantity"])
            if (
                pos_idx is not None
                and part_idx is not None
                and desc_idx is not None
                and qty_idx is not None
            ):
                header_idx = (pos_idx, part_idx, desc_idx, qty_idx)
            continue

        pos_idx, part_idx, desc_idx, qty_idx = header_idx
        assert (
            pos_idx is not None
            and part_idx is not None
            and desc_idx is not None
            and qty_idx is not None
        )

        position = _cell_str(row[pos_idx]) if pos_idx < len(row) else ""
        teilenummer = _cell_str(row[part_idx]) if part_idx < len(row) else ""
        bezeichnung = _cell_str(row[desc_idx]) if desc_idx < len(row) else ""
        menge = row[qty_idx] if qty_idx < len(row) else None

        # Leere Zeilen ueberspringen
        if not position and not teilenummer and not bezeichnung and menge in ("", None):
            continue
        if not position or not teilenummer:
            continue

        items.append(
            StkItem(
                position=position,
                teilenummer=teilenummer,
                bezeichnung=bezeichnung,
                menge=menge,
            )
        )

    wb.close()
    return items


def _to_number(value: Any) -> float:
    if value is None:
        return 0.0
    if isinstance(value, (int, float)):
        return float(value)
    text = str(value).strip().replace(",", ".")
    if not text:
        return 0.0
    try:
        return float(text)
    except ValueError:
        return 0.0


def _common_path_prefix(paths: list[str]) -> str:
    parts_list = [p.split("/") for p in paths if p]
    if not parts_list:
        return ""
    common = parts_list[0]
    for parts in parts_list[1:]:
        i = 0
        max_i = min(len(common), len(parts))
        while i < max_i and common[i] == parts[i]:
            i += 1
        common = common[:i]
        if not common:
            break
    return "/".join(common)


def _load_product_numbers(cfg_path: Path) -> dict[str, str]:
    """
    Laedt 9-stellige Produktnummern aus cfg/product_numbers.cfg.

    Erwartetes Format:
      [Sektion]
      123456789
      987654321
    """
    allowed_sections = {"omniflo", "ils", "cpc", "trolley", "geruest"}
    products: dict[str, str] = {}
    current_section = ""

    if not cfg_path.exists():
        return products

    for raw_line in cfg_path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#"):
            continue

        if line.startswith("[") and line.endswith("]"):
            current_section = line[1:-1].strip().lower()
            continue

        if current_section not in allowed_sections:
            continue

        if re.match(r"^\d{9}$", line):
            products[line] = current_section

    return products


def write_results(
    xlsx_path: Path,
    filtered_items: list[dict[str, Any]],
    log: logging.Logger,
    write_json: bool = False,
    write_csv: bool = False,
    write_xlsx: bool = False,
) -> None:
    if write_json:
        json_path = xlsx_path.with_suffix(".json")
        with json_path.open("w", encoding="utf-8") as fh:
            json.dump(filtered_items, fh, indent=2, ensure_ascii=False, default=str)
        log.info("JSON geschrieben: %s", json_path)

    if write_csv:
        csv_path = xlsx_path.with_suffix(".csv")
        with csv_path.open("w", encoding="utf-8-sig", newline="") as fh:
            writer = csv.writer(fh, delimiter=";")
            writer.writerow(["position", "teilenummer", "bezeichnung", "menge"])
            for row in filtered_items:
                writer.writerow(
                    [
                        row.get("position", ""),
                        row.get("teilenummer", ""),
                        row.get("bezeichnung", ""),
                        row.get("menge", ""),
                    ]
                )
        log.info("CSV geschrieben: %s", csv_path)

    if write_xlsx:
        import openpyxl

        xlsx_out = xlsx_path.with_name(f"{xlsx_path.stem}_result.xlsx")
        wb = openpyxl.Workbook()
        ws_3 = wb.active
        ws_3.title = "3er"
        ws_4 = wb.create_sheet("4er")
        ws_result = wb.create_sheet("Result")
        ws_product = wb.create_sheet("product")

        headers = ["position", "teilenummer", "bezeichnung", "menge"]
        ws_3.append(headers)
        ws_4.append(headers)

        aggregated: dict[str, dict[str, Any]] = defaultdict(
            lambda: {
                "teilenummer": "",
                "bezeichnung": "",
                "gesamtmenge": 0.0,
                "positions": [],
            }
        )

        for row in filtered_items:
            slash_count = str(row.get("position", "")).count("/")
            if slash_count == 3:
                ws_3.append([row.get(h, "") for h in headers])
            elif slash_count == 4:
                ws_4.append([row.get(h, "") for h in headers])

            tn = str(row.get("teilenummer", "")).strip()
            if not tn:
                continue
            if not aggregated[tn]["teilenummer"]:
                aggregated[tn]["teilenummer"] = tn
                aggregated[tn]["bezeichnung"] = row.get("bezeichnung", "")
            aggregated[tn]["gesamtmenge"] += _to_number(row.get("menge"))
            aggregated[tn]["positions"].append(str(row.get("position", "")).strip())

        ws_result.append(["teilenummer", "bezeichnung", "gesamtmenge", "mainpath", "endpath"])

        project_root = Path(__file__).resolve().parent.parent
        product_cfg = project_root / "cfg" / "product_numbers.cfg"
        product_numbers = _load_product_numbers(product_cfg)
        ws_product.append(["teilenummer", "bezeichnung", "gesamtmenge", "mainpath", "endpath"])

        for tn in sorted(aggregated.keys()):
            positions = [p for p in aggregated[tn]["positions"] if p]
            mainpath = _common_path_prefix(positions)
            variable_parts: list[str] = []
            for pos in positions:
                if mainpath:
                    suffix = pos[len(mainpath):].lstrip("/")
                else:
                    suffix = pos
                if suffix and suffix not in variable_parts:
                    variable_parts.append(suffix)
            endpath = ",".join(variable_parts)
            result_row = [
                aggregated[tn]["teilenummer"],
                aggregated[tn]["bezeichnung"],
                aggregated[tn]["gesamtmenge"],
                mainpath,
                endpath,
            ]
            ws_result.append(result_row)
            if tn in product_numbers:
                ws_product.append(result_row)

        wb.save(str(xlsx_out))
        wb.close()
        log.info("XLSX geschrieben: %s", xlsx_out)


def _process_single_folder(
    folder: Path,
    example_dir: Path,
    result: dict[str, list[dict[str, Any]]],
    log: logging.Logger,
    write_json: bool = False,
    write_csv: bool = False,
    write_xlsx: bool = False,
) -> None:
    xlsx_files = sorted(folder.glob("*.xlsx"))
    if not xlsx_files:
        return

    xlsx_path = xlsx_files[0]
    rel_folder = str(folder.relative_to(example_dir))
    try:
        items = _extract_stk_liste_ist(xlsx_path)
    except ValueError as exc:
        log.warning("%s/%s uebersprungen: %s", rel_folder, xlsx_path.name, exc)
        return
    size_path = (3, 4)

    filtered_items = [
        {
            "position": item.position,
            "teilenummer": item.teilenummer,
            "bezeichnung": item.bezeichnung,
            "menge": item.menge,
        }
        for item in items
        if item.position.count("/") in size_path
    ]
    result[rel_folder] = filtered_items
    write_results(
        xlsx_path,
        filtered_items,
        log,
        write_json=write_json,
        write_csv=write_csv,
        write_xlsx=write_xlsx,
    )

def process_all(
    example_dir: Path,
    write_json: bool = False,
    write_csv: bool = False,
    write_xlsx: bool = False,
) -> dict[str, list[dict[str, Any]]]:
    """
    Durchsucht rekursiv alle Unterverzeichnisse in example_dir.

    Pro Unterverzeichnis wird die erste gefundene .xlsx-Datei gelesen und aus
    dem Reiter "Stk.-Liste IST" werden Position, Teilenummer, Menge extrahiert.
    """
    if not example_dir.exists():
        log.error("Beispiel-Verzeichnis existiert nicht: %s", example_dir)
        sys.exit(1)

    subdirs = sorted([p for p in example_dir.rglob("*") if p.is_dir()])
    if not subdirs:
        log.error("Keine Unterverzeichnisse in %s gefunden", example_dir)
        sys.exit(1)

    log.info("Gefunden: %d Unterverzeichnisse in %s", len(subdirs), example_dir)

    result: dict[str, list[dict[str, Any]]] = {}
    for folder in subdirs:
        log.info("Verarbeite %s", folder)
        _process_single_folder(
            folder,
            example_dir,
            result,
            log,
            write_json=write_json,
            write_csv=write_csv,
            write_xlsx=write_xlsx,
        )
   
    log.info("Fertig. Gelesene Verzeichnisse mit Daten: %d", len(result))
    return result


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Liest Position/Teilenummer/Menge aus Example-Exceldateien"
    )
    parser.add_argument(
        "--example-dir",
        type=Path,
        default=None,
        help=(
            "Unterordnername in PV_EXAMPLES oder direkter Verzeichnispfad "
            "(default: alle Unterordner in PV_EXAMPLES)"
        ),
    )
    parser.add_argument("--verbose", "-v", action="store_true",
                        help="Ausfuehrliche Ausgabe")
    parser.add_argument(
        "--2json",
        dest="to_json",
        action="store_true",
        help="Schreibt Ausgabe als .json neben die jeweilige .xlsx",
    )
    parser.add_argument(
        "--2csv",
        dest="to_csv",
        action="store_true",
        help="Schreibt Ausgabe als .csv neben die jeweilige .xlsx",
    )
    parser.add_argument(
        "--2xlsx",
        dest="to_xlsx",
        action="store_true",
        help="Schreibt Ausgabe als _result.xlsx neben die jeweilige .xlsx",
    )
    args = parser.parse_args()

    logging.basicConfig(
        level=logging.DEBUG if args.verbose else logging.INFO,
        format="%(levelname)-7s %(message)s",
    )

    import os

    pv_examples = os.environ.get("PV_EXAMPLES")
    if not pv_examples:
        log.error("PV_EXAMPLES ist nicht gesetzt")
        sys.exit(1)

    examples_root = Path(pv_examples).resolve()
    if not examples_root.exists() or not examples_root.is_dir():
        log.error("PV_EXAMPLES ist kein gueltiges Verzeichnis: %s", examples_root)
        sys.exit(1)

    if args.example_dir is None:
        log.info("Beispiel-Verzeichnis (alle Unterordner): %s", examples_root)
        data = process_all(
            examples_root,
            write_json=args.to_json,
            write_csv=args.to_csv,
            write_xlsx=args.to_xlsx,
        )
        log.info("Objekt erzeugt mit %d Schluesseln", len(data))
        print(json.dumps(data, indent=2, ensure_ascii=False, default=str))
        return

    # --example-dir: entweder direkter Verzeichnispfad oder Unterordnername in PV_EXAMPLES
    candidate = args.example_dir
    if candidate.is_absolute():
        target_dir = candidate.resolve()
    else:
        as_path = candidate.resolve()
        if as_path.exists() and as_path.is_dir():
            target_dir = as_path
        else:
            target_dir = (examples_root / candidate).resolve()

    if not target_dir.exists() or not target_dir.is_dir():
        log.error(
            "--example-dir muss ein gueltiger Verzeichnispfad oder Unterordner in PV_EXAMPLES sein: %s",
            args.example_dir,
        )
        sys.exit(1)

    log.info("Beispiel-Verzeichnis (einzeln): %s", target_dir)
    result: dict[str, list[dict[str, Any]]] = {}
    _process_single_folder(
        target_dir,
        target_dir.parent,
        result,
        log,
        write_json=args.to_json,
        write_csv=args.to_csv,
        write_xlsx=args.to_xlsx,
    )
    log.info("Objekt erzeugt mit %d Schluesseln", len(result))
    #print(json.dumps(result, indent=2, ensure_ascii=False, default=str))


if __name__ == "__main__":
    main()
