"""
Mail-Fetcher Modul
Holt Attachments aus IMAP-Postfächern
"""
import imaplib
import email
from email.header import decode_header
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Optional
import logging

from ..config import INBOX_DIR

logger = logging.getLogger(__name__)


class MailFetcher:
    """Holt Attachments aus einem IMAP-Postfach"""

    def __init__(self, config: Dict):
        """
        Args:
            config: Dict mit imap_server, imap_port, email, passwort, ordner,
                   erlaubte_typen, max_groesse_mb
        """
        self.config = config
        self.connection = None

    def connect(self) -> bool:
        """Verbindung zum IMAP-Server herstellen"""
        try:
            self.connection = imaplib.IMAP4_SSL(
                self.config["imap_server"],
                self.config.get("imap_port", 993)
            )
            self.connection.login(
                self.config["email"],
                self.config["passwort"]
            )
            return True
        except Exception as e:
            logger.error(f"IMAP Verbindungsfehler: {e}")
            return False

    def disconnect(self):
        """Verbindung trennen"""
        if self.connection:
            try:
                self.connection.logout()
            except:
                pass
            self.connection = None

    def liste_ordner(self) -> List[str]:
        """Listet alle verfügbaren IMAP-Ordner"""
        if not self.connection:
            if not self.connect():
                return []

        try:
            status, folders = self.connection.list()
            ordner_liste = []
            if status == "OK":
                for folder in folders:
                    if isinstance(folder, bytes):
                        # Format: (flags) "delimiter" "name"
                        parts = folder.decode().split(' "')
                        if len(parts) >= 3:
                            name = parts[-1].strip('"')
                            ordner_liste.append(name)
                        else:
                            # Fallback
                            ordner_liste.append(folder.decode().split()[-1].strip('"'))
            return ordner_liste
        except Exception as e:
            logger.error(f"Fehler beim Auflisten der Ordner: {e}")
            return []

    def fetch_attachments(self, ziel_ordner: Optional[Path] = None,
                         nur_ungelesen: bool = False,
                         markiere_gelesen: bool = False,
                         alle_ordner: bool = False,
                         bereits_verarbeitet: set = None) -> List[Dict]:
        """
        Holt alle Attachments die den Filtern entsprechen

        Args:
            alle_ordner: Wenn True, werden ALLE IMAP-Ordner durchsucht
            bereits_verarbeitet: Set von Message-IDs die übersprungen werden

        Returns:
            Liste von Dicts mit: pfad, original_name, absender, betreff, datum, groesse, message_id
        """
        if not self.connection:
            if not self.connect():
                return []

        ziel = ziel_ordner or INBOX_DIR
        ziel.mkdir(parents=True, exist_ok=True)

        ergebnisse = []
        erlaubte_typen = self.config.get("erlaubte_typen", [".pdf"])
        max_groesse = self.config.get("max_groesse_mb", 25) * 1024 * 1024
        bereits_verarbeitet = bereits_verarbeitet or set()

        # Ordner bestimmen
        if alle_ordner:
            ordner_liste = self.liste_ordner()
            logger.info(f"Durchsuche {len(ordner_liste)} Ordner")
        else:
            ordner_liste = [self.config.get("ordner", "INBOX")]

        for ordner in ordner_liste:
            ergebnisse.extend(self._fetch_from_folder(
                ordner, ziel, erlaubte_typen, max_groesse,
                nur_ungelesen, markiere_gelesen, bereits_verarbeitet
            ))

        return ergebnisse

    def _fetch_from_folder(self, ordner: str, ziel: Path,
                           erlaubte_typen: List[str], max_groesse: int,
                           nur_ungelesen: bool, markiere_gelesen: bool,
                           bereits_verarbeitet: set) -> List[Dict]:
        """Holt Attachments aus einem einzelnen Ordner"""
        ergebnisse = []

        try:
            # Ordner auswählen
            status, _ = self.connection.select(ordner)

            # Suche nach Mails
            search_criteria = "(UNSEEN)" if nur_ungelesen else "ALL"
            status, messages = self.connection.search(None, search_criteria)

            if status != "OK":
                logger.warning(f"Keine Mails gefunden in {ordner}")
                return []

            mail_ids = messages[0].split()
            logger.info(f"Gefunden: {len(mail_ids)} Mails in {ordner}")

            for mail_id in mail_ids:
                try:
                    # Mail abrufen
                    status, msg_data = self.connection.fetch(mail_id, "(RFC822)")
                    if status != "OK":
                        continue

                    msg = email.message_from_bytes(msg_data[0][1])

                    # Message-ID extrahieren und prüfen ob bereits verarbeitet
                    message_id = msg.get("Message-ID", "")
                    if message_id and message_id in bereits_verarbeitet:
                        continue  # Bereits verarbeitet, überspringen

                    # Metadaten extrahieren
                    absender = self._decode_header(msg.get("From", ""))
                    betreff = self._decode_header(msg.get("Subject", ""))
                    datum = msg.get("Date", "")

                    # Attachments durchgehen
                    for part in msg.walk():
                        if part.get_content_maintype() == "multipart":
                            continue

                        filename = part.get_filename()
                        if not filename:
                            continue

                        filename = self._decode_header(filename)
                        datei_endung = Path(filename).suffix.lower()

                        # Filter prüfen
                        if datei_endung not in erlaubte_typen:
                            logger.debug(f"Überspringe {filename}: Typ {datei_endung} nicht erlaubt")
                            continue

                        payload = part.get_payload(decode=True)
                        if not payload:
                            continue

                        if len(payload) > max_groesse:
                            logger.warning(f"Überspringe {filename}: Zu groß ({len(payload)} bytes)")
                            continue

                        # Speichern
                        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                        safe_filename = self._safe_filename(filename)
                        ziel_pfad = ziel / f"{timestamp}_{safe_filename}"

                        # Eindeutigen Namen sicherstellen
                        counter = 1
                        while ziel_pfad.exists():
                            ziel_pfad = ziel / f"{timestamp}_{counter}_{safe_filename}"
                            counter += 1

                        ziel_pfad.write_bytes(payload)

                        ergebnisse.append({
                            "pfad": str(ziel_pfad),
                            "original_name": filename,
                            "absender": absender,
                            "betreff": betreff,
                            "datum": datum,
                            "groesse": len(payload),
                            "message_id": message_id,
                            "ordner": ordner
                        })

                        logger.info(f"Gespeichert: {ziel_pfad.name}")

                    # Als gelesen markieren
                    if markiere_gelesen and ergebnisse:
                        self.connection.store(mail_id, "+FLAGS", "\\Seen")

                except Exception as e:
                    logger.error(f"Fehler bei Mail {mail_id}: {e}")
                    continue

        except Exception as e:
            logger.error(f"Fehler beim Abrufen: {e}")

        return ergebnisse

    def _decode_header(self, value: str) -> str:
        """Dekodiert Email-Header (kann encoded sein)"""
        if not value:
            return ""
        try:
            decoded_parts = decode_header(value)
            result = []
            for part, charset in decoded_parts:
                if isinstance(part, bytes):
                    result.append(part.decode(charset or "utf-8", errors="replace"))
                else:
                    result.append(part)
            return " ".join(result)
        except:
            return str(value)

    def _safe_filename(self, filename: str) -> str:
        """Macht Dateinamen sicher für Dateisystem"""
        # Ungültige Zeichen ersetzen
        invalid_chars = '<>:"/\\|?*'
        for char in invalid_chars:
            filename = filename.replace(char, "_")
        return filename.strip()

    def fetch_attachments_generator(self, ziel_ordner: Optional[Path] = None,
                                      nur_ungelesen: bool = False,
                                      markiere_gelesen: bool = False,
                                      alle_ordner: bool = False,
                                      bereits_verarbeitet: set = None):
        """
        Generator-Version für Streaming - yielded Events während des Abrufs

        Yields:
            Dict mit type: "ordner", "mails", "datei", "skip", "fehler"
        """
        if not self.connection:
            if not self.connect():
                yield {"type": "fehler", "nachricht": "Verbindung fehlgeschlagen"}
                return

        ziel = ziel_ordner or INBOX_DIR
        ziel.mkdir(parents=True, exist_ok=True)

        erlaubte_typen = self.config.get("erlaubte_typen", [".pdf"])
        max_groesse = self.config.get("max_groesse_mb", 25) * 1024 * 1024
        bereits_verarbeitet = bereits_verarbeitet or set()

        # Ordner bestimmen
        if alle_ordner:
            ordner_liste = self.liste_ordner()
            yield {"type": "info", "nachricht": f"{len(ordner_liste)} Ordner gefunden"}
        else:
            ordner_liste = [self.config.get("ordner", "INBOX")]

        for ordner in ordner_liste:
            yield {"type": "ordner", "name": ordner}

            try:
                status, _ = self.connection.select(ordner)
                search_criteria = "(UNSEEN)" if nur_ungelesen else "ALL"
                status, messages = self.connection.search(None, search_criteria)

                if status != "OK":
                    continue

                mail_ids = messages[0].split()
                yield {"type": "mails", "ordner": ordner, "anzahl": len(mail_ids)}

                for mail_id in mail_ids:
                    try:
                        status, msg_data = self.connection.fetch(mail_id, "(RFC822)")
                        if status != "OK":
                            continue

                        msg = email.message_from_bytes(msg_data[0][1])
                        message_id = msg.get("Message-ID", "")

                        if message_id and message_id in bereits_verarbeitet:
                            continue

                        absender = self._decode_header(msg.get("From", ""))
                        betreff = self._decode_header(msg.get("Subject", ""))
                        datum = msg.get("Date", "")

                        for part in msg.walk():
                            if part.get_content_maintype() == "multipart":
                                continue

                            filename = part.get_filename()
                            if not filename:
                                continue

                            filename = self._decode_header(filename)
                            datei_endung = Path(filename).suffix.lower()

                            if datei_endung not in erlaubte_typen:
                                continue

                            payload = part.get_payload(decode=True)
                            if not payload:
                                continue

                            if len(payload) > max_groesse:
                                yield {"type": "skip", "datei": filename, "grund": "zu groß"}
                                continue

                            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                            safe_filename = self._safe_filename(filename)
                            ziel_pfad = ziel / f"{timestamp}_{safe_filename}"

                            counter = 1
                            while ziel_pfad.exists():
                                ziel_pfad = ziel / f"{timestamp}_{counter}_{safe_filename}"
                                counter += 1

                            ziel_pfad.write_bytes(payload)

                            yield {
                                "type": "datei",
                                "pfad": str(ziel_pfad),
                                "original_name": filename,
                                "absender": absender,
                                "betreff": betreff[:100] if betreff else "",
                                "datum": datum,
                                "groesse": len(payload),
                                "message_id": message_id,
                                "ordner": ordner
                            }

                        if markiere_gelesen:
                            self.connection.store(mail_id, "+FLAGS", "\\Seen")

                    except Exception as e:
                        yield {"type": "fehler", "nachricht": f"Mail-Fehler: {str(e)[:100]}"}
                        continue

            except Exception as e:
                yield {"type": "fehler", "nachricht": f"Ordner-Fehler {ordner}: {str(e)[:100]}"}

    def test_connection(self) -> Dict:
        """Testet die Verbindung und gibt Status zurück"""
        try:
            if self.connect():
                # Ordner auflisten
                status, folders = self.connection.list()
                ordner_liste = []
                if status == "OK":
                    for folder in folders:
                        if isinstance(folder, bytes):
                            ordner_liste.append(folder.decode())
                self.disconnect()
                return {
                    "erfolg": True,
                    "nachricht": "Verbindung erfolgreich",
                    "ordner": ordner_liste
                }
            else:
                return {
                    "erfolg": False,
                    "nachricht": "Verbindung fehlgeschlagen"
                }
        except Exception as e:
            return {
                "erfolg": False,
                "nachricht": str(e)
            }
