import os
import sys
from pathlib import Path
from sys import platform
from typing import Tuple, Type
from pydantic import Field
from pydantic_yaml import to_yaml_str
from ruamel.yaml import YAML
from enum import Enum
import logging

from pydantic import BaseModel
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, JsonConfigSettingsSource


logger = logging.getLogger(__name__)

app_name = "DocuMentor"


if platform == "win32":
    tmp_config_path = f"%APPDATA%\\{app_name}\\config.json"
elif platform in ("linux", "linux2"):
    tmp_config_path = f"~/.config/{app_name}/config.json"
elif platform == "darwin":
    tmp_config_path = f"~/Library/Application Support/{app_name}/͏͏͏͏config.json"
else:
    tmp_config_path = f"~/.config/{app_name}/config.json"

config_path = Path(os.path.expandvars(tmp_config_path)).expanduser()


class JavaVm(BaseModel):
    id: int
    version: str
    path_to_binary_file: Path


class DiffPdf(BaseModel):
    id: int
    version: str
    path_to_binary_file: Path
    default_params: list[str]
    output_file_extension: str = "pdf"


class SaxonJar(BaseModel):
    id: int
    version: str
    path_to_jar_file: Path
    output_file_extension: str = "fo"


class ApacheFop(BaseModel):
    id: int
    version: str
    path_to_dir: Path
    output_file_extension: str = "pdf"


class XslDir(BaseModel):
    id: int
    name: str
    path_to_root_dir: Path


class SSLMode(str, Enum):
    DISABLE = "disable"
    ALLOW = "allow"
    PREFER = "prefer"
    REQUIRE = "require"
    VERIFY_CA = "verify-ca"
    VERIFY_FULL = "verify-full"


class XsltVersion(str, Enum):
    """XSLT-Version für Saxon-Transformationen."""

    XSLT_1_0 = "1.0"  # JAXP API (nur XSLT 1.0)
    XSLT_2_0_3_0 = "2.0/3.0"  # s9api (XSLT 2.0 und 3.0)


class GraphLayout(str, Enum):
    """vis.js Physics-Solver / Layout-Modus."""

    BARNES_HUT = "barnesHut"
    FORCE_ATLAS2 = "forceAtlas2Based"
    REPULSION = "repulsion"
    HIERARCHICAL = "hierarchical"


class HierarchicalDirection(str, Enum):
    """Richtung für hierarchisches Layout."""

    UD = "UD"
    DU = "DU"
    LR = "LR"
    RL = "RL"


class HierarchicalSortMethod(str, Enum):
    """Sortiermethode für hierarchisches Layout."""

    HUBSIZE = "hubsize"
    DIRECTED = "directed"


class GraphLayoutSettings(BaseModel):
    """Persistierte vis.js Layout-Einstellungen für den XSL-Abhängigkeitsgraph."""

    layout: GraphLayout = GraphLayout.BARNES_HUT

    # barnesHut
    bh_gravitational_constant: int = -3000
    bh_central_gravity: float = 0.3
    bh_spring_length: int = 150
    bh_spring_constant: float = 0.04
    bh_damping: float = 0.09

    # forceAtlas2Based
    fa_gravitational_constant: int = -50
    fa_central_gravity: float = 0.01
    fa_spring_length: int = 100
    fa_spring_constant: float = 0.08
    fa_damping: float = 0.4

    # repulsion
    re_node_distance: int = 120
    re_central_gravity: float = 0.0
    re_spring_length: int = 200
    re_spring_constant: float = 0.05
    re_damping: float = 0.09

    # hierarchical
    hi_direction: HierarchicalDirection = HierarchicalDirection.UD
    hi_sort_method: HierarchicalSortMethod = HierarchicalSortMethod.HUBSIZE
    hi_level_separation: int = 150
    hi_node_spacing: int = 100
    hi_tree_spacing: int = 200


class PostgreSqlDb(BaseModel):
    id: int
    name: str
    host: str
    port: int = 5432
    database: str
    username: str
    password: str
    ssl_mode: SSLMode = SSLMode.PREFER
    timeout: int = 10


class Project(BaseModel):
    id: int = Field(..., description="Eindeutige Projekt-ID", gt=0)
    name: str = Field(..., description="Projekt-Name", min_length=1, max_length=255)
    project_dir: Path = Field(..., description="Pfad zum Projekt-Verzeichnis")
    java_vm_id: int = Field(..., description="ID der Java VM", gt=0)
    diff_pdf_id: int = Field(..., description="ID der diff-pdf Konfiguration", gt=0)
    saxon_jar_id: int = Field(..., description="ID der Saxon JAR Konfiguration", gt=0)
    apache_fop_id: int = Field(..., description="ID der Apache FOP Konfiguration", gt=0)
    xsl_dir_id: int = Field(..., description="ID des XSL-Verzeichnisses", gt=0)
    postgre_sql_db_id: int = Field(..., description="ID der PostgreSQL Datenbank", gt=0)
    fop_config_dir: Path | None = Field(None, description="Optionaler Pfad zum Apache FOP Config-Verzeichnis")
    xslt_params: dict[str, str] = Field(default_factory=dict, description="Projektweite XSLT-Parameter")

    @staticmethod
    def _lookup(collection, item_id: int, attr: str) -> str:
        """Sucht einen Wert in einer Konfigurationsliste anhand der ID."""
        value = [getattr(x, attr) for x in collection if x.id == item_id]
        return value[0] if value else ""

    def getXsl(self) -> str:
        return self._lookup(app_settings.xsl_dirs, self.xsl_dir_id, "name")

    def getJavaVm(self) -> str:
        return self._lookup(app_settings.java_vms, self.java_vm_id, "version")

    def getSaxon(self) -> str:
        return self._lookup(app_settings.saxon_jars, self.saxon_jar_id, "version")

    def getApacheFop(self) -> str:
        return self._lookup(app_settings.apache_fops, self.apache_fop_id, "version")

    def getDiffPdf(self) -> str:
        return self._lookup(app_settings.diff_pdfs, self.diff_pdf_id, "version")

    def getPostgreSqlDb(self) -> str:
        return self._lookup(app_settings.postgresql_dbs, self.postgre_sql_db_id, "name")


class AppSettings(BaseSettings):
    java_vms: list[JavaVm] = []
    diff_pdfs: list[DiffPdf] = []
    saxon_jars: list[SaxonJar] = []
    apache_fops: list[ApacheFop] = []
    xsl_dirs: list[XslDir] = []
    pdf_projects: list[Project] = []
    postgresql_dbs: list[PostgreSqlDb] = []
    theme: str | None = None
    max_workers: int = 8  # Anzahl paralleler Worker für Transformationen (Standard: 8)
    use_saxon_worker_pool: bool = True  # SaxonWorkerPool aktivieren (schneller, benötigt JDK)
    saxon_xslt_version: XsltVersion = XsltVersion.XSLT_2_0_3_0  # XSLT-Version für Saxon (Standard: 2.0/3.0 mit s9api)
    use_fop_worker_pool: bool = True  # FopWorkerPool aktivieren (schneller, benötigt JDK)

    # UI-Zustand
    window_geometry: tuple[int, int, int, int] | None = None  # (x, y, width, height)
    splitter_sizes: list[int] | None = None  # Splitter-Positionen
    tree_column_widths: list[int] | None = None  # TreeWidget-Spaltenbreiten
    graph_layout_settings: GraphLayoutSettings = Field(default_factory=GraphLayoutSettings)

    model_config = SettingsConfigDict(json_file=config_path)

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: Type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> Tuple[PydanticBaseSettingsSource, ...]:
        return (JsonConfigSettingsSource(settings_cls),)

    def save(self):
        # Ordner existert nicht
        if not config_path.parent.exists():
            config_path.parent.mkdir(parents=True, exist_ok=True)

        if not config_path.parent.is_dir() or not os.access(config_path.parent, os.W_OK):
            logger.exception(f"{config_path.parent} ist kein Verzeichnis oder es gibt keine Schreibrechte")
            sys.exit(1)

        # Konfiguration speichern
        with open(config_path, "wb") as c:
            c.write(app_settings.model_dump_json(indent=4).encode())


app_settings = AppSettings()


class XmlFile(BaseModel):
    xml: Path
    # blake2b hashsum
    hashsum: str | None = None


class XslFile(BaseModel):
    id: tuple
    bez: str
    xsl_file: Path
    xslt_params: dict[str, str] = {}
    xmls: list[XmlFile] = []


class TreeNode(BaseModel):
    id: tuple
    bez: str
    xslt_params: dict[str, str] = {}
    children: list["TreeNode|XslFile"]


class ProjectData(BaseModel):
    """
    Speichert die Projekteinstellungen direkt im Projektordner in einer .yaml-Datei.
    """

    nodes: list[TreeNode] = []
    expanded_nodes: list[tuple] | None = None  # Optional: IDs der aufgeklappten Knoten (TreeNode und XslFile)

    @classmethod
    def readSettings(cls, project_dir: Path):
        # Explizit UTF-8 Encoding verwenden
        project_yaml_path = project_dir / "project.yaml"
        with open(project_yaml_path, "r", encoding="utf-8") as f:
            yaml = YAML(typ="safe")
            yaml_data = yaml.load(f)
            return cls.model_validate(yaml_data)

    def writeSettings(self, project_dir: Path):
        with open(project_dir / "project.yaml", "w", encoding="utf8") as f:
            f.write(to_yaml_str(self))
