from __future__ import annotations

import logging
import logging.handlers
import os
from datetime import datetime, timedelta
from pathlib import Path
from typing import Iterable, List, Tuple, Dict, Optional, Any

from .config_loader import Settings


DEFAULT_FORMAT = "%(asctime)s | %(levelname)s | %(name)s | %(filename)s:%(lineno)d | %(message)s"
DEFAULT_DATEFMT = "%Y-%m-%d %H:%M:%S"


def init_logging(settings: Settings, app_config: Optional[Dict[str, Any]] = None) -> None:
    """
    Initialisiert das Logging gemäß den Laufzeit-Settings.

    Erfüllt die Anforderungen:
    - Unter logs/ je Tag eine neue Datei (TimedRotatingFileHandler -> midnight).
    - Konfigurierbare Aufbewahrungsdauer (retention_days).
    - Konfigurierbare maximale Log-Größe (interpretiert als maximale Gesamtgröße des Log-Verzeichnisses);
      älteste Dateien werden bei Überschreitung entfernt.

    Hinweise:
    - Diese Funktion setzt das Root-Logging neu (entfernt bestehende Handler), um konsistentes Verhalten
      gegenüber evtl. vorhandener dictConfig zu gewährleisten.
    - Zusätzlich zur Datei wird immer ein Console-Handler eingerichtet.
    """
    params = settings.get_logging_params()
    level_name: str = params.get("level", "INFO")
    level = getattr(logging, level_name.upper(), logging.INFO)

    # Verzeichnis vorbereiten
    raw_log_dir: str = params.get("log_dir", "logs")
    log_dir: Path = settings.resolve_path(raw_log_dir)
    log_dir.mkdir(parents=True, exist_ok=True)

    # Root-Logger zurücksetzen und konfigurieren
    root = logging.getLogger()
    _reset_logger_handlers(root)
    root.setLevel(level)

    # Formatter
    formatter = logging.Formatter(fmt=DEFAULT_FORMAT, datefmt=DEFAULT_DATEFMT)

    # Console-Handler
    console_handler = logging.StreamHandler()
    console_handler.setLevel(level)
    console_handler.setFormatter(formatter)
    root.addHandler(console_handler)

    # Datei-Handler: tägliche Rotation um Mitternacht
    logfile = log_dir / "app.log"
    file_handler = logging.handlers.TimedRotatingFileHandler(
        filename=str(logfile),
        when="midnight",
        interval=1,
        backupCount=int(params.get("retention_days", 7)),
        encoding="utf-8",
        utc=False,
    )
    file_handler.setLevel(level)
    file_handler.setFormatter(formatter)
    root.addHandler(file_handler)

    # Cleanup-Regeln anwenden (maximale Gesamtgröße und zusätzliche tagbasierte Aufbewahrung)
    try:
        _cleanup_logs(
            log_dir=log_dir,
            max_total_mb=int(params.get("max_log_size_mb", params.get("max_log_size", 10))),
            retention_days=int(params.get("retention_days", 7)),
            file_prefix="app",
            extensions=(".log",),
        )
    except Exception:  # Schutz vor Start-Abbruch durch Aufräumfehler
        logging.getLogger(__name__).exception("Fehler beim Log-Cleanup ignoriert.")

    # Optionale Integration: internes SQLite-Logging (01_Modulerweiterungen)
    try:
        internal_cfg = ((app_config or {}).get("logging_internal") or {})
    except Exception:
        internal_cfg = {}

    if isinstance(internal_cfg, dict) and internal_cfg.get("enabled"):
        try:
            import importlib
            mod = importlib.import_module("logging_internal")
        except Exception:
            logging.getLogger(__name__).warning(
                "logging_internal Modul nicht importierbar; interner DB-Handler wird nicht aktiviert."
            )
        else:
            try:
                db_path = internal_cfg.get("db_path", "data/internal_logs.sqlite")
                clean_db = bool(internal_cfg.get("clean_database", False))
                retention_days_cfg = int(internal_cfg.get("retention_days", 30))
                max_entries_cfg = int(internal_cfg.get("max_entries", 100000))
                vacuum_on_start = bool(internal_cfg.get("vacuum_on_start", True))

                resolved_db_path = settings.resolve_path(str(db_path))

                mod.init(
                    db_path=str(resolved_db_path),
                    vacuum_on_start=vacuum_on_start,
                    clean_database=clean_db,
                    retention_days=retention_days_cfg,
                    max_entries=max_entries_cfg,
                )
                handler = mod.get_engineered_handler(level)
                root.addHandler(handler)
                logging.getLogger(__name__).info("Interner SQLite-Log-Handler aktiv: %s", str(resolved_db_path))
            except Exception:
                logging.getLogger(__name__).exception(
                    "Fehler bei Initialisierung des internen SQLite-Loggings; Handler wird nicht aktiviert."
                )

    # Optionale Integration: externes DB-Logging (01_Modulerweiterungen)
    try:
        external_cfg = ((app_config or {}).get("logging_external") or {})
    except Exception:
        external_cfg = {}

    if isinstance(external_cfg, dict) and external_cfg.get("enabled"):
        try:
            import importlib
            ext_mod = importlib.import_module("logging_external")
        except Exception:
            logging.getLogger(__name__).warning(
                "logging_external Modul nicht importierbar; externer DB-Handler wird nicht aktiviert."
            )
        else:
            try:
                # ext_mod.init akzeptiert entweder connection_url ODER config-Dict
                # Wir übergeben das gesamte Config-Dict und lassen das Modul die URL bauen.
                ext_mod.init(config=external_cfg)
                ext_handler = ext_mod.get_handler(level)
                root.addHandler(ext_handler)
                logging.getLogger(__name__).info("Externer DB-Log-Handler aktiviert.")
            except Exception:
                logging.getLogger(__name__).exception(
                    "Fehler bei Initialisierung des externen DB-Loggings; Handler wird nicht aktiviert."
                )

def _reset_logger_handlers(logger: logging.Logger) -> None:
    """Entfernt alle existierenden Handler vom Logger."""
    for h in list(logger.handlers):
        try:
            logger.removeHandler(h)
            try:
                h.close()
            except Exception:
                pass
        except Exception:
            pass


def _cleanup_logs(
    log_dir: Path,
    max_total_mb: int,
    retention_days: int,
    file_prefix: str = "app",
    extensions: Tuple[str, ...] = (".log",),
) -> None:
    """
    Bereinigt das Log-Verzeichnis nach zwei Regeln:

    1) Zeitbasierte Aufbewahrung (retention_days):
       - Löscht Dateien, deren Änderungszeitpunkt älter als retention_days ist.

    2) Größenlimit (max_total_mb):
       - Sicherstellt, dass die Gesamtgröße aller Log-Dateien (passend zu extensions/prefix)
         das Limit nicht überschreitet. Bei Überschreitung werden die ältesten Dateien
         entfernt, bis das Limit eingehalten wird.

    Diese Regeln ergänzen den TimedRotatingFileHandler (backupCount), der ohnehin
    nur eine feste Anzahl von Backups behält. Mit diesem zusätzlichen Cleanup
    wird explizit die Aufbewahrungsdauer (in Tagen) und die Gesamtgröße kontrolliert.
    """
    if max_total_mb <= 0 and retention_days <= 0:
        return

    candidates: List[Path] = _collect_log_files(log_dir, file_prefix, extensions)

    # 1) Zeitbasierte Aufbewahrung (zusätzlich zu backupCount)
    if retention_days > 0:
        cutoff = datetime.now() - timedelta(days=retention_days)
        for f in candidates:
            try:
                mtime = datetime.fromtimestamp(f.stat().st_mtime)
                if mtime < cutoff:
                    _safe_unlink(f)
            except FileNotFoundError:
                # Wurde zwischenzeitlich rotiert/gelöscht
                continue

    # Refresh nach Löschungen
    candidates = _collect_log_files(log_dir, file_prefix, extensions)

    # 2) Größenlimit
    if max_total_mb > 0:
        limit_bytes = max_total_mb * 1024 * 1024
        # Sortiere nach mtime aufsteigend (älteste zuerst)
        sorted_by_age = sorted(
            candidates,
            key=lambda p: (p.stat().st_mtime if p.exists() else float("inf")),
        )
        total_size = _total_size(sorted_by_age)

        for f in sorted_by_age:
            if total_size <= limit_bytes:
                break
            try:
                size_before = f.stat().st_size if f.exists() else 0
                _safe_unlink(f)
                total_size -= size_before
            except FileNotFoundError:
                # Bereits gelöscht
                continue


def _collect_log_files(log_dir: Path, prefix: str, exts: Tuple[str, ...]) -> List[Path]:
    if not log_dir.exists():
        return []
    result: List[Path] = []
    try:
        for item in log_dir.iterdir():
            if not item.is_file():
                continue
            if not item.suffix.lower() in exts:
                continue
            # Akzeptiere Standard-Dateinamen und Rotationssuffixe (z. B. app.log, app.log.2025-11-13, ...)
            name = item.name
            if name.startswith(prefix):
                result.append(item)
    except Exception:
        # Defensive: bei Problemen einfach keine Kandidaten liefern
        return []
    return result


def _total_size(files: Iterable[Path]) -> int:
    s = 0
    for f in files:
        try:
            s += f.stat().st_size
        except FileNotFoundError:
            continue
    return s


def _safe_unlink(p: Path) -> None:
    try:
        p.unlink(missing_ok=True)  # py3.8+: attribute missing_ok in 3.8? Actually 3.8+: No, use exists() check
    except TypeError:
        # Fallback für Python-Versionen ohne missing_ok
        if p.exists():
            os.remove(p)


__all__ = ["init_logging"]