"""
Parser für THIRD_PARTY_LICENSES.txt.

Extrahiert strukturierte Lizenzinformationen und ergänzt sie
mit den tatsächlich installierten Paketversionen via importlib.metadata
oder (im PyInstaller-Bundle) aus der mitgebündelten versions.json.
"""
import json
import logging
import re
import sys
from dataclasses import dataclass, field
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path

logger = logging.getLogger(__name__)

# Pfad zur Lizenzdatei: im PyInstaller-Bundle aus sys._MEIPASS,
# im Entwicklungsmodus relativ zum Projektroot
if hasattr(sys, "_MEIPASS"):
    LICENSE_FILE = Path(sys._MEIPASS) / "THIRD_PARTY_LICENSES.txt"  # type: ignore[attr-defined]
else:
    LICENSE_FILE = Path(__file__).parent.parent / "THIRD_PARTY_LICENSES.txt"

# Mapping von Anzeigenamen zu PyPI-Paketnamen für Sonderfälle
_PACKAGE_NAME_MAP: dict[str, str] = {
    "PySide6": "PySide6",
    "Pydantic": "pydantic",
    "Pydantic-Settings": "pydantic-settings",
    "Pydantic-YAML": "pydantic-yaml",
    "Polars": "polars",
    "ConnectorX (via Polars)": "connectorx",
    "PyArrow (via Polars)": "pyarrow",
    "psutil": "psutil",
    "lxml": "lxml",
    "Ruff (Development)": "ruff",
    "PyInstaller (Development)": "pyinstaller",
    "Pillow (Development)": "pillow",
}

# Regex für den Beginn eines nummerierten Eintrags (z.B. "1. PySide6" oder "10. PyInstaller (Development)")
_ENTRY_PATTERN = re.compile(r"^\d+\.\s+(.+)$")

# Bekannte Feldschlüssel in der Lizenzdatei
_KNOWN_KEYS = {"Version", "Lizenz", "Webseite", "GitHub", "Beschreibung", "Copyright", "Datei", "Hinweis"}


@dataclass
class LicenseEntry:
    """Ein einzelner Eintrag aus THIRD_PARTY_LICENSES.txt."""

    name: str
    license: str = ""
    installed_version: str = ""
    website: str = ""
    description: str = ""
    copyright: str = ""
    category: str = ""


@dataclass
class ParsedLicenses:
    """Ergebnis des Parsens der Lizenzdatei."""

    entries: list[LicenseEntry] = field(default_factory=list)


def _normalize_package_name(display_name: str) -> str:
    """Normalisiert einen Anzeigenamen zu einem PyPI-Paketnamen."""
    if display_name in _PACKAGE_NAME_MAP:
        return _PACKAGE_NAME_MAP[display_name]
    # Fallback: lowercase, Leerzeichen/Klammern entfernen
    return display_name.lower().split("(")[0].strip()


_bundled_versions: dict[str, str] | None = None


def _load_bundled_versions() -> dict[str, str]:
    """Lädt den Versions-Snapshot aus der mitgebündelten versions.json (nur im PyInstaller-Bundle)."""
    global _bundled_versions
    if _bundled_versions is None:
        if hasattr(sys, "_MEIPASS"):
            versions_file = Path(sys._MEIPASS) / "versions.json"  # type: ignore[attr-defined]
            if versions_file.exists():
                _bundled_versions = json.loads(versions_file.read_text(encoding="utf-8"))
            else:
                _bundled_versions = {}
        else:
            _bundled_versions = {}
    return _bundled_versions


def _get_installed_version(package_name: str) -> str:
    """Ermittelt die installierte Version eines Pakets."""
    bundled = _load_bundled_versions()
    if bundled:
        return bundled.get(package_name.lower(), "")
    try:
        return version(package_name)
    except PackageNotFoundError:
        return ""


def parse_license_file(license_file: Path | None = None) -> ParsedLicenses:
    """
    Parst THIRD_PARTY_LICENSES.txt und gibt strukturierte Einträge zurück.

    Args:
        license_file: Pfad zur Lizenzdatei. Standard: LICENSE_FILE

    Returns:
        ParsedLicenses mit allen gefundenen Einträgen inkl. installierter Versionen
    """
    if license_file is None:
        license_file = LICENSE_FILE

    if not license_file.exists():
        logger.warning(f"Lizenzdatei nicht gefunden: {license_file}")
        return ParsedLicenses()

    text = license_file.read_text(encoding="utf-8")
    lines = text.splitlines()

    result = ParsedLicenses()
    current_category = ""
    current_entry: LicenseEntry | None = None
    last_key: str | None = None  # Letzter erkannter Schlüssel (für Fortsetzungszeilen)
    separator_line = "=" * 20  # Mindestlänge für Sektions-Trennlinien

    i = 0
    while i < len(lines):
        line = lines[i]

        # Sektions-Trennlinie erkannt → nächste Zeile ist Sektionsname
        if line.startswith(separator_line):
            i += 1
            if i < len(lines):
                section_name = lines[i].strip()

                # Bei "Lizenztexte" aufhören — ab hier kommen nur noch Volltexte
                if section_name == "Lizenztexte":
                    # Letzten Eintrag noch sichern
                    if current_entry:
                        result.entries.append(current_entry)
                    break

                # Neue Kategorie setzen (ignoriere den Datei-Header "THIRD PARTY LICENSES")
                if section_name != "THIRD PARTY LICENSES":
                    current_category = section_name

            # Schließende Trennlinie der Sektion überspringen
            i += 1
            if i < len(lines) and lines[i].startswith(separator_line):
                i += 1
            continue

        # Nummerierter Eintrag (z.B. "1. PySide6")
        match = _ENTRY_PATTERN.match(line.strip())
        if match:
            # Vorherigen Eintrag speichern
            if current_entry:
                result.entries.append(current_entry)

            entry_name = match.group(1).strip()
            current_entry = LicenseEntry(name=entry_name, category=current_category)

            # Installierte Version ermitteln
            pypi_name = _normalize_package_name(entry_name)
            current_entry.installed_version = _get_installed_version(pypi_name)

            i += 1
            continue

        # Schlüssel-Wert-Zeile innerhalb eines Eintrags (z.B. "   Version: >=6.10.1")
        stripped = line.strip()
        if current_entry and ":" in stripped:
            key, _, value = stripped.partition(":")
            key = key.strip()
            value = value.strip()

            if key in _KNOWN_KEYS:
                last_key = key
                if key == "Lizenz":
                    current_entry.license = value
                elif key == "Webseite":
                    current_entry.website = value
                elif key == "GitHub" and not current_entry.website:
                    # GitHub nur als Fallback wenn keine Webseite vorhanden
                    current_entry.website = value
                elif key == "Beschreibung":
                    current_entry.description = value
                elif key == "Copyright":
                    current_entry.copyright = value
            else:
                last_key = None
        elif current_entry and stripped and not stripped.startswith("="):
            # Fortsetzungszeile (z.B. mehrzeiliger Copyright-Eintrag)
            if last_key == "Copyright":
                current_entry.copyright += "\n" + stripped

        i += 1

    # Letzten Eintrag sichern (falls Datei nicht mit "Lizenztexte" endet)
    if current_entry and current_entry not in result.entries:
        result.entries.append(current_entry)

    logger.info(f"{len(result.entries)} Lizenzeinträge aus {license_file.name} geladen")
    return result


if __name__ == "__main__":
    # Standalone-Test
    logging.basicConfig(level=logging.DEBUG)
    parsed = parse_license_file()
    for entry in parsed.entries:
        ver = entry.installed_version or "—"
        logger.debug(f"[{entry.category}] {entry.name}: {ver} ({entry.license})")
