"""PDF generation service using Jinja2 templates and WeasyPrint."""

from __future__ import annotations

import io
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from jinja2 import Environment, FileSystemLoader, select_autoescape

# ─── Constants ──────────────────────────────────────────────────────────────

TEMPLATES_DIR = Path(__file__).parent / "templates"

PRESET_TEMPLATES: dict[str, str] = {
    "contact_list": "contact_list.html.j2",
    "calendar_week": "calendar_week.html.j2",
    "calendar_month": "calendar_month.html.j2",
    "company_list": "company_list.html.j2",
    "audit_log": "audit_log.html.j2",
}

PRESET_META: list[dict[str, Any]] = [
    {
        "key": "contact_list",
        "name": "Kontaktliste",
        "description": "Liste aller Kontakte mit Name, E-Mail, Telefon und Typ",
        "icon": "Users",
        "output_formats": ["pdf", "print", "csv", "excel"],
    },
    {
        "key": "calendar_week",
        "name": "Wochenkalender",
        "description": "Wochenkalender mit Tagen und Stunden",
        "icon": "Calendar",
        "output_formats": ["pdf", "print"],
    },
    {
        "key": "calendar_month",
        "name": "Monatskalender",
        "description": "Monatskalender mit allen Terminen",
        "icon": "CalendarDays",
        "output_formats": ["pdf", "print"],
    },
    {
        "key": "company_list",
        "name": "Firmenliste",
        "description": "Liste aller Firmen mit Adresse und Ansprechpartner",
        "icon": "Building2",
        "output_formats": ["pdf", "print", "csv", "excel"],
    },
    {
        "key": "audit_log",
        "name": "Audit-Log",
        "description": "Audit-Log mit Timestamp, User, Action und Entity",
        "icon": "ShieldCheck",
        "output_formats": ["pdf", "print", "csv", "excel"],
    },
]

# ─── Jinja2 Environment ─────────────────────────────────────────────────────


def _get_env() -> Environment:
    """Create a Jinja2 environment with file system loader for templates dir."""
    return Environment(
        loader=FileSystemLoader(str(TEMPLATES_DIR)),
        autoescape=select_autoescape(["html", "htm", "j2", "xml"]),
        trim_blocks=True,
        lstrip_blocks=True,
    )


# ─── Public API ─────────────────────────────────────────────────────────────


def get_preset_list() -> list[dict[str, Any]]:
    """Return metadata for all preset report templates."""
    return PRESET_META.copy()


def render_template_file(template_name: str, data: dict[str, Any]) -> str:
    """Render a Jinja2 template file from the templates directory.

    Args:
        template_name: Filename inside templates/ (e.g. 'contact_list.html.j2')
        data: Template variables

    Returns:
        Rendered HTML string
    """
    env = _get_env()
    template = env.get_template(template_name)
    # Inject generated_at if not provided
    if "generated_at" not in data:
        data["generated_at"] = datetime.now(timezone.utc).strftime(
            "%Y-%m-%d %H:%M UTC"
        )
    return template.render(**data)


def render_template_string(template_content: str, data: dict[str, Any]) -> str:
    """Render a Jinja2 template string (for user-defined templates).

    Args:
        template_content: Raw Jinja2 template string
        data: Template variables

    Returns:
        Rendered HTML string
    """
    env = Environment(
        autoescape=select_autoescape(["html", "htm", "xml"]),
        trim_blocks=True,
        lstrip_blocks=True,
    )
    template = env.from_string(template_content)
    if "generated_at" not in data:
        data["generated_at"] = datetime.now(timezone.utc).strftime(
            "%Y-%m-%d %H:%M UTC"
        )
    return template.render(**data)


def generate_pdf(html_content: str) -> bytes:
    """Generate a PDF from HTML content using WeasyPrint.

    Args:
        html_content: Valid HTML string

    Returns:
        PDF bytes
    """
    from weasyprint import HTML

    pdf = HTML(string=html_content).write_pdf()
    return pdf


def generate_print_pdf(html_content: str) -> bytes:
    """Generate a print-optimized PDF (same as PDF but triggers print dialog CSS).

    The 'print' format produces a PDF with additional @media print styles.
    Since WeasyPrint already respects @media print rules, this is functionally
    identical to generate_pdf but semantically distinct for the API consumer.

    Args:
        html_content: Valid HTML string

    Returns:
        PDF bytes
    """
    return generate_pdf(html_content)


def generate_preset_report(
    preset_key: str, output_format: str, parameters: dict[str, Any]
) -> tuple[bytes, str]:
    """Generate a preset report by key.

    Args:
        preset_key: One of PRESET_TEMPLATES keys
        output_format: 'pdf', 'print', 'csv', 'excel', or 'json'
        parameters: Template data parameters

    Returns:
        Tuple of (file_bytes, file_extension)
    """
    if preset_key not in PRESET_TEMPLATES:
        raise ValueError(f"Unknown preset: {preset_key}")

    template_file = PRESET_TEMPLATES[preset_key]
    html = render_template_file(template_file, parameters)

    if output_format in ("pdf", "print"):
        pdf_bytes = generate_pdf(html) if output_format == "pdf" else generate_print_pdf(html)
        return pdf_bytes, "pdf"
    elif output_format == "csv":
        # For CSV, extract table data from parameters directly
        import csv

        output = io.StringIO()
        writer = csv.writer(output)
        # Write based on preset type
        if preset_key == "contact_list":
            writer.writerow(["#", "Name", "E-Mail", "Telefon", "Typ", "Firma"])
            for i, c in enumerate(parameters.get("contacts", []), 1):
                writer.writerow([
                    i,
                    c.get("name", ""),
                    c.get("email", ""),
                    c.get("phone", ""),
                    c.get("type", ""),
                    c.get("company", ""),
                ])
        elif preset_key == "company_list":
            writer.writerow(["#", "Firmenname", "Adresse", "PLZ", "Ort", "Telefon", "E-Mail", "Ansprechpartner"])
            for i, c in enumerate(parameters.get("companies", []), 1):
                writer.writerow([
                    i,
                    c.get("name", ""),
                    c.get("address", ""),
                    c.get("zip", ""),
                    c.get("city", ""),
                    c.get("phone", ""),
                    c.get("email", ""),
                    c.get("contact_person", ""),
                ])
        elif preset_key == "audit_log":
            writer.writerow(["#", "Zeitstempel", "Benutzer", "Aktion", "Entität", "Entität-ID", "Details"])
            for i, e in enumerate(parameters.get("entries", []), 1):
                writer.writerow([
                    i,
                    e.get("timestamp", ""),
                    e.get("user", ""),
                    e.get("action", ""),
                    e.get("entity", ""),
                    e.get("entity_id", ""),
                    e.get("details", ""),
                ])
        else:
            writer.writerow(["Data"])
            writer.writerow([parameters])
        return output.getvalue().encode("utf-8"), "csv"
    elif output_format == "excel":
        from openpyxl import Workbook

        wb = Workbook()
        ws = wb.active
        ws.title = "Report"
        if preset_key == "contact_list":
            ws.append(["#", "Name", "E-Mail", "Telefon", "Typ", "Firma"])
            for i, c in enumerate(parameters.get("contacts", []), 1):
                ws.append([i, c.get("name", ""), c.get("email", ""), c.get("phone", ""), c.get("type", ""), c.get("company", "")])
        elif preset_key == "company_list":
            ws.append(["#", "Firmenname", "Adresse", "PLZ", "Ort", "Telefon", "E-Mail", "Ansprechpartner"])
            for i, c in enumerate(parameters.get("companies", []), 1):
                ws.append([i, c.get("name", ""), c.get("address", ""), c.get("zip", ""), c.get("city", ""), c.get("phone", ""), c.get("email", ""), c.get("contact_person", "")])
        elif preset_key == "audit_log":
            ws.append(["#", "Zeitstempel", "Benutzer", "Aktion", "Entität", "Entität-ID", "Details"])
            for i, e in enumerate(parameters.get("entries", []), 1):
                ws.append([i, e.get("timestamp", ""), e.get("user", ""), e.get("action", ""), e.get("entity", ""), e.get("entity_id", ""), e.get("details", "")])
        else:
            ws.append(["Data"])
            ws.append([str(parameters)])
        buf = io.BytesIO()
        wb.save(buf)
        return buf.getvalue(), "xlsx"
    elif output_format == "json":
        import json

        return json.dumps(parameters, indent=2, ensure_ascii=False).encode("utf-8"), "json"
    else:
        raise ValueError(f"Unsupported output format: {output_format}")


def generate_pdf_from_template_content(
    template_content: str, data: dict[str, Any], output_format: str = "pdf"
) -> tuple[bytes, str]:
    """Generate a report from user-defined Jinja2 template content.

    Args:
        template_content: Raw Jinja2 template string (HTML for PDF, text for CSV/JSON)
        data: Template variables
        output_format: 'pdf', 'print', 'csv', 'excel', or 'json'

    Returns:
        Tuple of (file_bytes, file_extension)
    """
    rendered = render_template_string(template_content, data)

    if output_format in ("pdf", "print"):
        pdf_bytes = generate_pdf(rendered) if output_format == "pdf" else generate_print_pdf(rendered)
        return pdf_bytes, "pdf"
    elif output_format == "csv":
        import csv

        output = io.StringIO()
        reader = csv.reader(io.StringIO(rendered))
        writer = csv.writer(output)
        for row in reader:
            writer.writerow(row)
        return output.getvalue().encode("utf-8"), "csv"
    elif output_format == "excel":
        from openpyxl import Workbook

        wb = Workbook()
        ws = wb.active
        ws.title = "Report"
        import csv

        reader = csv.reader(io.StringIO(rendered))
        for row in reader:
            ws.append(row)
        buf = io.BytesIO()
        wb.save(buf)
        return buf.getvalue(), "xlsx"
    elif output_format == "json":
        import json

        try:
            return json.dumps(json.loads(rendered), indent=2, ensure_ascii=False).encode("utf-8"), "json"
        except json.JSONDecodeError:
            return json.dumps({"result": rendered}, indent=2, ensure_ascii=False).encode("utf-8"), "json"
    else:
        raise ValueError(f"Unsupported output format: {output_format}")
