"""
Pipeline Service
Orchestriert die gesamte Dokumentenverarbeitung
"""
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Optional
import logging

from sqlalchemy.orm import Session

from ..models import Pipeline, MailConfig, SortierRegel, Dokument, VerarbeitungsLog
from ..modules.mail_fetcher import MailFetcher
from ..modules.pdf_processor import PDFProcessor
from ..modules.sorter import Sorter
from ..config import INBOX_DIR, PROCESSED_DIR, ZUGFERD_DIR

logger = logging.getLogger(__name__)


class PipelineService:
    """Führt die komplette Pipeline-Verarbeitung durch"""

    def __init__(self, db: Session):
        self.db = db
        self.pdf_processor = PDFProcessor()

    def verarbeite_pipeline(self, pipeline_id: int) -> Dict:
        """
        Führt alle Schritte einer Pipeline aus

        Returns:
            Dict mit Statistiken und Ergebnissen
        """
        pipeline = self.db.query(Pipeline).filter(Pipeline.id == pipeline_id).first()
        if not pipeline:
            return {"fehler": f"Pipeline {pipeline_id} nicht gefunden"}

        if not pipeline.aktiv:
            return {"fehler": f"Pipeline {pipeline.name} ist deaktiviert"}

        ergebnis = {
            "pipeline": pipeline.name,
            "gestartet": datetime.now().isoformat(),
            "mails_abgerufen": 0,
            "attachments": 0,
            "verarbeitet": 0,
            "zugferd": 0,
            "ocr": 0,
            "sortiert": 0,
            "fehler": []
        }

        # 1. Mails abrufen
        inbox_pfad = INBOX_DIR / f"pipeline_{pipeline_id}"
        inbox_pfad.mkdir(parents=True, exist_ok=True)

        for mail_config in pipeline.mail_configs:
            if not mail_config.aktiv:
                continue

            try:
                attachments = self._rufe_mails_ab(mail_config, inbox_pfad)
                ergebnis["attachments"] += len(attachments)

                # Dokumente in DB anlegen
                for att in attachments:
                    dokument = Dokument(
                        pipeline_id=pipeline_id,
                        original_name=att["original_name"],
                        original_pfad=att["pfad"],
                        status="neu",
                        extrahierte_daten={
                            "absender": att.get("absender"),
                            "betreff": att.get("betreff"),
                            "mail_datum": att.get("datum")
                        }
                    )
                    self.db.add(dokument)
                    self._log(dokument.id, "mail_abruf", "erfolg", att)

                # Letzten Abruf aktualisieren
                mail_config.letzter_abruf = datetime.utcnow()

            except Exception as e:
                ergebnis["fehler"].append(f"Mail-Abruf {mail_config.name}: {e}")
                logger.error(f"Mail-Abruf Fehler: {e}")

        self.db.commit()

        # 2. PDFs verarbeiten
        neue_dokumente = self.db.query(Dokument).filter(
            Dokument.pipeline_id == pipeline_id,
            Dokument.status == "neu"
        ).all()

        for dokument in neue_dokumente:
            try:
                self._verarbeite_dokument(dokument, pipeline)
                ergebnis["verarbeitet"] += 1

                if dokument.ist_zugferd:
                    ergebnis["zugferd"] += 1
                if dokument.ocr_durchgefuehrt:
                    ergebnis["ocr"] += 1
                if dokument.status == "sortiert":
                    ergebnis["sortiert"] += 1

            except Exception as e:
                dokument.status = "fehler"
                dokument.fehler_meldung = str(e)
                ergebnis["fehler"].append(f"Verarbeitung {dokument.original_name}: {e}")
                logger.error(f"Verarbeitungs-Fehler: {e}")

        self.db.commit()

        ergebnis["beendet"] = datetime.now().isoformat()
        return ergebnis

    def _rufe_mails_ab(self, mail_config: MailConfig, ziel_ordner: Path) -> List[Dict]:
        """Ruft Mails von einem Postfach ab"""
        config = {
            "imap_server": mail_config.imap_server,
            "imap_port": mail_config.imap_port,
            "email": mail_config.email,
            "passwort": mail_config.passwort,
            "ordner": mail_config.ordner,
            "erlaubte_typen": mail_config.erlaubte_typen,
            "max_groesse_mb": mail_config.max_groesse_mb
        }

        fetcher = MailFetcher(config)
        try:
            attachments = fetcher.fetch_attachments(ziel_ordner)
            return attachments
        finally:
            fetcher.disconnect()

    def _verarbeite_dokument(self, dokument: Dokument, pipeline: Pipeline):
        """Verarbeitet ein einzelnes Dokument"""
        pfad = Path(dokument.original_pfad)

        if not pfad.exists():
            raise FileNotFoundError(f"Datei nicht gefunden: {pfad}")

        # Nur PDFs verarbeiten
        if pfad.suffix.lower() != ".pdf":
            dokument.status = "uebersprungen"
            self._log(dokument.id, "verarbeitung", "uebersprungen", {"grund": "Kein PDF"})
            return

        # PDF verarbeiten
        pdf_ergebnis = self.pdf_processor.verarbeite(str(pfad))

        if "fehler" in pdf_ergebnis:
            raise Exception(pdf_ergebnis["fehler"])

        dokument.ist_zugferd = pdf_ergebnis["ist_zugferd"]
        dokument.hat_text = pdf_ergebnis["hat_text"]
        dokument.ocr_durchgefuehrt = pdf_ergebnis["ocr_durchgefuehrt"]

        # ZUGFeRD separat behandeln - NICHT umbenennen!
        if dokument.ist_zugferd:
            self._behandle_zugferd(dokument, pdf_ergebnis)
            return

        # Sortieren
        self._sortiere_dokument(dokument, pdf_ergebnis, pipeline)

    def _behandle_zugferd(self, dokument: Dokument, pdf_ergebnis: Dict):
        """Behandelt ZUGFeRD-Rechnungen (werden nicht verändert)"""
        # In separaten Ordner verschieben
        ziel_dir = ZUGFERD_DIR / datetime.now().strftime("%Y-%m")
        ziel_dir.mkdir(parents=True, exist_ok=True)

        quell_pfad = Path(dokument.original_pfad)
        ziel_pfad = ziel_dir / quell_pfad.name

        # Eindeutigen Namen sicherstellen
        counter = 1
        while ziel_pfad.exists():
            ziel_pfad = ziel_dir / f"{quell_pfad.stem}_{counter}{quell_pfad.suffix}"
            counter += 1

        # Verschieben (nicht umbenennen!)
        import shutil
        shutil.move(str(quell_pfad), str(ziel_pfad))

        dokument.neuer_pfad = str(ziel_pfad)
        dokument.neuer_name = ziel_pfad.name
        dokument.status = "zugferd"
        dokument.extrahierte_daten = {
            **(dokument.extrahierte_daten or {}),
            "zugferd_xml": pdf_ergebnis.get("zugferd_xml")
        }

        self._log(dokument.id, "zugferd", "erfolg", {
            "ziel": str(ziel_pfad),
            "hinweis": "ZUGFeRD wird nicht umbenannt"
        })

    def _sortiere_dokument(self, dokument: Dokument, pdf_ergebnis: Dict, pipeline: Pipeline):
        """Sortiert Dokument nach Regeln"""
        # Regeln laden
        regeln = self.db.query(SortierRegel).filter(
            SortierRegel.pipeline_id == pipeline.id,
            SortierRegel.aktiv == True
        ).order_by(SortierRegel.prioritaet).all()

        # In Sorter-Format konvertieren
        regeln_dicts = []
        for r in regeln:
            regeln_dicts.append({
                "id": r.id,
                "name": r.name,
                "prioritaet": r.prioritaet,
                "aktiv": r.aktiv,
                "muster": r.muster,
                "extraktion": r.extraktion,
                "schema": r.schema,
                "ziel_ordner": r.ziel_ordner
            })

        sorter = Sorter(regeln_dicts)

        # Dokument-Info zusammenstellen
        dokument_info = {
            "text": pdf_ergebnis.get("text", ""),
            "original_name": dokument.original_name,
            "absender": (dokument.extrahierte_daten or {}).get("absender", ""),
            "betreff": (dokument.extrahierte_daten or {}).get("betreff", "")
        }

        # Passende Regel finden
        regel = sorter.finde_passende_regel(dokument_info)

        if not regel:
            dokument.status = "keine_regel"
            self._log(dokument.id, "sortierung", "keine_regel", {})
            return

        # Felder extrahieren
        extrahiert = sorter.extrahiere_felder(regel, dokument_info)

        # Dateinamen generieren
        neuer_name = sorter.generiere_dateinamen(regel, extrahiert)

        # Zielordner bestimmen
        ziel_ordner = regel.get("ziel_ordner") or str(PROCESSED_DIR / pipeline.name)

        # Verschieben
        neuer_pfad = sorter.verschiebe_datei(
            dokument.original_pfad,
            ziel_ordner,
            neuer_name
        )

        # Dokument aktualisieren
        dokument.neuer_name = neuer_name
        dokument.neuer_pfad = neuer_pfad
        dokument.extrahierte_daten = {
            **(dokument.extrahierte_daten or {}),
            "text_auszug": pdf_ergebnis.get("text", "")[:500],
            **extrahiert
        }
        dokument.regel_id = regel.get("id")
        dokument.status = "sortiert"
        dokument.verarbeitet = datetime.utcnow()

        self._log(dokument.id, "sortierung", "erfolg", {
            "regel": regel.get("name"),
            "neuer_name": neuer_name,
            "ziel": neuer_pfad
        })

    def _log(self, dokument_id: int, schritt: str, status: str, details: Dict):
        """Erstellt Log-Eintrag"""
        log = VerarbeitungsLog(
            dokument_id=dokument_id,
            schritt=schritt,
            status=status,
            details=details
        )
        self.db.add(log)


class PipelineManager:
    """Verwaltet Pipelines (CRUD-Operationen)"""

    def __init__(self, db: Session):
        self.db = db

    def erstelle_pipeline(self, name: str, beschreibung: str = "") -> Pipeline:
        """Erstellt neue Pipeline"""
        pipeline = Pipeline(name=name, beschreibung=beschreibung)
        self.db.add(pipeline)
        self.db.commit()
        self.db.refresh(pipeline)
        return pipeline

    def hole_alle_pipelines(self) -> List[Pipeline]:
        """Gibt alle Pipelines zurück"""
        return self.db.query(Pipeline).all()

    def hole_pipeline(self, pipeline_id: int) -> Optional[Pipeline]:
        """Gibt eine Pipeline zurück"""
        return self.db.query(Pipeline).filter(Pipeline.id == pipeline_id).first()

    def fuege_mail_config_hinzu(self, pipeline_id: int, config: Dict) -> MailConfig:
        """Fügt Mail-Konfiguration zu Pipeline hinzu"""
        mail_config = MailConfig(
            pipeline_id=pipeline_id,
            name=config.get("name", "Unbenannt"),
            imap_server=config["imap_server"],
            imap_port=config.get("imap_port", 993),
            email=config["email"],
            passwort=config["passwort"],
            ordner=config.get("ordner", "INBOX"),
            erlaubte_typen=config.get("erlaubte_typen", [".pdf"]),
            max_groesse_mb=config.get("max_groesse_mb", 25)
        )
        self.db.add(mail_config)
        self.db.commit()
        self.db.refresh(mail_config)
        return mail_config

    def fuege_regel_hinzu(self, pipeline_id: int, regel: Dict) -> SortierRegel:
        """Fügt Sortier-Regel zu Pipeline hinzu"""
        sortier_regel = SortierRegel(
            pipeline_id=pipeline_id,
            name=regel["name"],
            prioritaet=regel.get("prioritaet", 100),
            muster=regel.get("muster", {}),
            extraktion=regel.get("extraktion", {}),
            schema=regel.get("schema", "{datum} - Dokument.pdf"),
            ziel_ordner=regel.get("ziel_ordner")
        )
        self.db.add(sortier_regel)
        self.db.commit()
        self.db.refresh(sortier_regel)
        return sortier_regel

    def teste_regel(self, regel: Dict, text: str) -> Dict:
        """Testet eine Regel gegen einen Text"""
        sorter = Sorter([regel])
        dokument_info = {"text": text, "original_name": "test.pdf", "absender": ""}

        passend = sorter.finde_passende_regel(dokument_info) is not None

        extrahiert = {}
        dateiname = ""
        if passend:
            extrahiert = sorter.extrahiere_felder(regel, dokument_info)
            dateiname = sorter.generiere_dateinamen(regel, extrahiert)

        return {
            "regel_passt": passend,
            "extrahierte_felder": extrahiert,
            "vorgeschlagener_name": dateiname
        }
