"""DOCX-Rechnungserstellung: Template befüllen, Platzhalter ersetzen, Akzentfarbe setzen."""

import os
import logging
import pandas as pd

from core.models import Mitglied, SEX_MALE, SEX_FEMALE, DEFAULT_DATE

logger = logging.getLogger(__name__)


def render_invoice_docx(
    system,
    mitglied: Mitglied,
    template_path: str = None,
    output_path: str = None,
) -> str:
    """Rendert eine Rechnung aus dem DOCX-Template via docxtpl.

    system: Abrechnungssystem-Instanz (liefert config, folder_rechnungen, _build_docx_context).
    Gibt den absoluten Pfad der geschriebenen DOCX-Datei zurück.
    """
    from docxtpl import DocxTemplate

    if template_path is None:
        primary = os.path.join(
            system.main_path, "Config",
            system.config.get("File_Rechnungsvorlage_DOCX", "Rechnung_Default.docx"),
        )
        if os.path.exists(primary):
            template_path = primary
        else:
            # Fallback: Built-in neben main.py
            builtin = os.path.join(
                os.path.dirname(os.path.abspath(__file__)),
                "..", "sepa_templates", "Rechnung_Default.docx",
            )
            if os.path.exists(builtin):
                template_path = builtin
                logger.info(f"DOCX-Template aus Config/ nicht gefunden – nutze Built-in: {builtin}")
            else:
                template_path = primary

    if not os.path.exists(template_path):
        raise FileNotFoundError(f"DOCX-Template nicht gefunden: {template_path}")

    if output_path is None:
        output_path = os.path.join(
            system.folder_rechnungen,
            f"Rechnung_{mitglied.Rechnungsnummer}_{mitglied.Name4Rechnung}.docx",
        )
    output_path = os.path.abspath(output_path)

    try:
        doc     = DocxTemplate(template_path)
        context = system._build_docx_context(mitglied)

        # Logo: InlineImage muss nach dem DocxTemplate-Objekt erzeugt werden
        logo_img = _build_logo_image(doc, system)
        if logo_img is not None:
            context["logo"] = logo_img

        doc.render(context)
        doc.save(output_path)

        # Akzentfarbe nachträglich anwenden
        accent = str(system.config.get("Accent_Color", "")).strip()
        if accent:
            try:
                _apply_accent_color(output_path, accent)
            except Exception as e:
                logger.warning(f"Akzentfarbe konnte nicht angewendet werden: {e}")

        logger.info(f"DOCX-Rechnung erstellt: {os.path.basename(output_path)}")
        return output_path

    except Exception as e:
        logger.error(f"Fehler beim DOCX-Rendering für {mitglied.Name}: {e}")
        raise


def fill_template(system, mitglied: Mitglied) -> str:
    """Füllt die Excel-Vorlage (Legacy-Pfad) mit Mitgliedsdaten und speichert sie.

    Gibt den absoluten Pfad der geschriebenen XLSX-Datei zurück.
    """
    template_path = os.path.join(
        system.main_path, "Config", system.config["File_Rechnungsvorlage"]
    )
    output_path = os.path.join(
        system.folder_rechnungen,
        f"Rechnung_{mitglied.Rechnungsnummer}_{mitglied.Name4Rechnung}.xlsx",
    )
    output_path = os.path.abspath(output_path)
    if os.name == "nt":
        output_path = output_path.replace("/", "\\")

    try:
        from openpyxl import load_workbook as _lw
        wb = _lw(template_path, read_only=False, keep_links=False)
        ws = wb.active
        _replace_placeholders(ws, mitglied, system.config, system.datum_rechnung,
                              system.datum_abbuchung)
        wb.save(output_path)
        wb.close()
        return output_path
    except Exception as e:
        logger.error(f"Fehler beim Füllen der Vorlage für {mitglied.Name}: {e}")
        raise


def _replace_placeholders(ws, mitglied: Mitglied, config: dict, datum_rechnung, datum_abbuchung) -> None:
    """Ersetzt alle [Platzhalter] in der Excel-Vorlage."""
    from core.utils import mask_iban

    replacements = {
        "[Vereinsname]":     config["Vereinsname"],
        "[Vereinsstrasse]":  config["Vereinsstrasse"],
        "[Vereinsadresse]":  config["Vereinsstrasse"],
        "[Vereinsort]":      config["Vereinsort"],
        "[PLZ_Ort]":         config["Vereinsort"],
        "[SEPA_IBAN]":       config["SEPA_IBAN"],
        "[VereinsIBAN]":     config["SEPA_IBAN"],
        "[SEPA_BIC]":        config.get("SEPA_BIC", ""),
        "[Bank]":            config["Bank"],
        "[SEPA_Creditor_ID]": config["SEPA_Creditor_ID"],
        "[Creditor_ID]":     config["SEPA_Creditor_ID"],
        "[ZVR]":             str(config.get("ZVR", "")),
        "[Mail_Adresse]":    config.get("Mail_Adresse", ""),
        "[Anschrift]":       mitglied.Name4Rechnung if mitglied.Name4Rechnung else mitglied.Name,
        "[Straße]":          mitglied.Strasse,
        "[PLZ]":             mitglied.PLZ,
        "[Ort]":             mitglied.Ort,
        "[Rechnungsnummer]": mitglied.Rechnungsnummer,
        "[Rechnungsdatum]":  datum_rechnung.strftime("%d.%m.%Y"),
        "[Fälligkeitsdatum]": datum_abbuchung.strftime("%d.%m.%Y"),
        "[Anrede]":          ("Lieber" if mitglied.Sex == SEX_MALE
                              else "Liebe" if mitglied.Sex == SEX_FEMALE
                              else "Liebe/r"),
        "[Name]":            mitglied.Name,
        "[Consumption aktiv]": (mitglied.ZP_Consumption[-10:]
                                if pd.notna(mitglied.ZP_Consumption) else "-"),
        "[Consumption von]":  (mitglied.Start_C.strftime("%d.%m.%Y")
                               if mitglied.Consumption > 0
                               else mitglied.Start_G.strftime("%d.%m.%Y")),
        "[Consumption bis]":  (mitglied.Ende_C.strftime("%d.%m.%Y")
                               if mitglied.Consumption > 0
                               else mitglied.Ende_G.strftime("%d.%m.%Y")),
        "[Consumption]":      f"{mitglied.Consumption:.1f}" if mitglied.Consumption > 0 else "0",
        "[Tarif Bezug]":      config["Tarif_Bezug"].replace(".", ","),
        "[Kosten Consumption]": (
            f"{mitglied.Consumption * float(config['Tarif_Bezug'].replace(',', '.')):.2f}"
            if mitglied.Consumption > 0 else "0"
        ),
        "[Generation aktiv]": (mitglied.ZP_Generation[-10:]
                               if pd.notna(mitglied.ZP_Generation) else "-"),
        "[Generation von]":   (mitglied.Start_G.strftime("%d.%m.%Y")
                               if mitglied.Generation > 0
                               else mitglied.Start_C.strftime("%d.%m.%Y")),
        "[Generation bis]":   (mitglied.Ende_G.strftime("%d.%m.%Y")
                               if mitglied.Generation > 0
                               else mitglied.Ende_C.strftime("%d.%m.%Y")),
        "[Generation]":       f"- {mitglied.Generation:.1f}" if mitglied.Generation > 0 else "0",
        "[Tarif Einspeisung]": config["Tarif_Einspeisung"].replace(".", ","),
        "[Kosten Generation]": (
            f"- {mitglied.Generation * float(config['Tarif_Einspeisung'].replace(',', '.')):.2f}"
            if mitglied.Generation > 0 else "0"
        ),
        "[Gesamtkosten]":     f"{mitglied.Kosten:.2f}",
        "[Kontonummer]":      mask_iban(mitglied.IBAN),
    }

    for row in ws.iter_rows():
        for cell in row:
            if cell.value and isinstance(cell.value, str):
                for placeholder, replacement in replacements.items():
                    cell.value = cell.value.replace(placeholder, str(replacement))


def _build_logo_image(doc, system):
    """Erstellt ein docxtpl InlineImage-Objekt für das Vereinslogo, oder None wenn nicht gefunden."""
    logo_cfg = (system.config.get("Logo_Path", "") or system.config.get("Logo_Filename", ""))
    logo_cfg = str(logo_cfg).strip()
    if not logo_cfg:
        return None

    candidates = []
    if os.path.isabs(logo_cfg):
        candidates.append(logo_cfg)
    else:
        candidates.append(os.path.join(system.main_path, "Config", logo_cfg))
        candidates.append(os.path.join(system.main_path, logo_cfg))

    logo_path = next((p for p in candidates if os.path.exists(p)), None)
    if logo_path is None:
        logger.warning(f"Logo-Datei nicht gefunden (probiert: {candidates}).")
        return None

    try:
        from docxtpl import InlineImage
        from docx.shared import Mm
        return InlineImage(doc, logo_path, width=Mm(30))
    except Exception as e:
        logger.warning(f"Logo konnte nicht geladen werden ({logo_path}): {e}")
        return None


def _apply_accent_color(docx_path: str, hex_color: str) -> None:
    """Setzt die Akzentfarbe auf ausgewählte Text-Runs im bereits gerenderten DOCX."""
    from docx import Document as _Doc
    from docx.shared import RGBColor

    h = hex_color.strip().lstrip("#")
    if len(h) != 6 or not all(c in "0123456789abcdefABCDEF" for c in h):
        raise ValueError(f"Ungültige Hex-Farbe: {hex_color!r}")
    rgb = RGBColor(int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))

    doc = _Doc(docx_path)

    accent_markers = ["RECHNUNG", "Gesamtkosten", "Kostenaufstellung"]

    def _colorize_runs_in_paragraph(p):
        for run in p.runs:
            if any(mk.lower() in (run.text or "").lower() for mk in accent_markers):
                run.font.color.rgb = rgb

    for p in doc.paragraphs:
        _colorize_runs_in_paragraph(p)

    def _walk_table(t):
        for row in t.rows:
            for cell in row.cells:
                for p in cell.paragraphs:
                    _colorize_runs_in_paragraph(p)
                for nested in cell.tables:
                    _walk_table(nested)

    for t in doc.tables:
        _walk_table(t)

    doc.save(docx_path)
