"""Die zentrale Erweiterung: haelt alles zusammen, was eine App braucht.

Statt globaler Modulvariablen (der Vorgaenger hatte ein Dutzend davon, u. a.
``_license_state`` und die DB-Pfade) haengt der gesamte Anwendungszustand an
einem Objekt in ``app.extensions["tesm"]``. Zugriff ueber ``core()``.
"""

from __future__ import annotations

import sqlite3
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Sequence

from flask import Flask, current_app, g

from .config import CoreConfig
from .db import Database
from .keystore import Keystore
from .migrations import Migration, MigrationRunner
from .nav import NavTree
from .rbac.model import PermissionTree, RolePreset
from .security.passwords import PasswordPolicy
from .settings_store import SettingsStore

if TYPE_CHECKING:  # pragma: no cover
    from .licensing_client import LicenseManager
    from .sysops import SysOps


@dataclass
class TesmCore:
    """Anwendungsweiter Zustand. Nach ``init_app`` unveraendert."""

    config: CoreConfig
    database: Database
    keystore: Keystore
    permissions: PermissionTree
    nav: NavTree
    settings: SettingsStore
    role_presets: tuple[RolePreset, ...] = ()
    migrations: dict[str, Sequence[Migration]] = field(default_factory=dict)
    license: "LicenseManager | None" = None
    sysops: "SysOps | None" = None
    #: App-spezifische Erweiterungen (z. B. Papierkorb-Register, Job-Registry).
    registry: dict[str, Any] = field(default_factory=dict)
    #: Wird nach einer erfolgreichen Anmeldung aufgerufen:
    #: ``(conn, user, secret, group_dns)``. ``secret`` ist das Passwort dieser
    #: Anmeldung -- es wird nirgends gespeichert und ist nach dem Aufruf weg.
    #: Ein Haken, der scheitert, sperrt niemanden aus (siehe
    #: ``auth.service._run_login_hooks``).
    login_hooks: list[Callable[..., None]] = field(default_factory=list)
    #: Wird vor dem Verwerfen der Sitzung aufgerufen: ``(conn, user)``. Was die
    #: Anmeldung aufgebaut hat, wird hier wieder abgebaut -- sonst bliebe es
    #: bestehen, bis eine Zeitgrenze greift.
    logout_hooks: list[Callable[..., None]] = field(default_factory=list)

    @property
    def password_policy(self) -> PasswordPolicy:
        return PasswordPolicy(min_length=self.config.security.password_min_length)

    @property
    def audit_archive_dir(self) -> Path:
        return self.config.log_dir / "audit"

    def run_migrations(self) -> list[str]:
        runner = MigrationRunner(self.database)
        applied: list[str] = []
        for component, migrations in self.migrations.items():
            applied.extend(runner.run(component, migrations))
        return applied

    def migration_status(self) -> list[dict[str, object]]:
        return MigrationRunner(self.database).status(list(self.migrations))


def core(app: Flask | None = None) -> TesmCore:
    target = app or current_app
    try:
        return target.extensions["tesm"]  # type: ignore[return-value]
    except KeyError as exc:  # pragma: no cover - Programmierfehler
        raise RuntimeError("tesm_core ist fuer diese Anwendung nicht initialisiert") from exc


def db() -> sqlite3.Connection:
    """Die Verbindung dieses Requests. Genau eine pro Request, nie eine zweite.

    Diese Funktion ist der einzige zugelassene Weg, im Request-Kontext an eine
    Verbindung zu kommen. Wer stattdessen selbst ``Database.connect()`` aufruft,
    riskiert genau den Deadlock, an dem der Vorgaenger mehrfach hing.
    """
    connection = getattr(g, "_tesm_conn", None)
    if connection is None:
        connection = core().database.connect()
        g._tesm_conn = connection
    return connection


def close_db(_exception: BaseException | None = None) -> None:
    connection = getattr(g, "_tesm_conn", None)
    if connection is not None:
        g._tesm_conn = None
        connection.close()


def transaction() -> Any:
    """Kurzform fuer ``core().database.transaction(db())``."""
    return core().database.transaction(db())


def with_transaction(fn: Callable[..., Any]) -> Callable[..., Any]:
    """Dekorator: fuehrt eine Funktion innerhalb einer Schreibtransaktion aus."""

    def wrapper(*args: Any, **kwargs: Any) -> Any:
        with transaction() as conn:
            return fn(conn, *args, **kwargs)

    wrapper.__name__ = fn.__name__
    wrapper.__doc__ = fn.__doc__
    return wrapper
