from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from zipfile import ZipFile
from xml.etree import ElementTree as ET

from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from app.models.report_template import ChecklistTemplate, ReportSection, ReportTemplate, TextBlock
from app.modules.orion.html import text

TEMPLATE_KEY = "small_steam_sterilizer_initial_validation"
TEMPLATE_NAME = "Erstvalidierung Klein-Sterilisator"
TEMPLATE_VERSION = "1.0"
REFERENCE_PATH = "docs/reference/reports/Erstvalidierung_Dr.Durmaz_Steri_12-25.docx"

REPORT_SECTIONS: list[dict[str, Any]] = [
    {"key": "functional_qualification", "number": "1", "title": "Funktionsqualifikation (BQ)", "toc": True, "component": "static", "block_key": None},
    {"key": "purpose", "number": "1.1", "title": "Anlass und Ziel der Prüfung", "toc": True, "component": "static", "block_key": "bq_goal"},
    {"key": "legal", "number": "1.2", "title": "Gesetzliche Grundlagen", "toc": True, "component": "static", "block_key": "legal"},
    {"key": "device", "number": "1.3", "title": "Angaben zum Gerät", "toc": True, "component": "device"},
    {"key": "performance", "number": "1.4", "title": "Leistungsüberprüfung", "toc": True, "component": "static", "block_key": "performance"},
    {"key": "performance_checklist", "number": "1.5", "title": "Checkliste Leistungsanforderung", "toc": True, "component": "checklist"},
    {"key": "documentation", "number": "1.6", "title": "Dokumentation / Kontrolle", "toc": True, "component": "static"},
    {"key": "work_instructions", "number": "1.7", "title": "Arbeitsanweisungen", "toc": True, "component": "static"},
    {"key": "environment", "number": "1.8", "title": "Umgebungsbedingungen", "toc": True, "component": "environment"},
    {"key": "batch_control", "number": "1.9", "title": "Chargenkontrolle", "toc": True, "component": "static"},
    {"key": "programs", "number": "1.10", "title": "Beschreibung der verwendeten Programme", "toc": True, "component": "programs"},
    {"key": "loading", "number": "1.11", "title": "Beladungsbeschreibungen", "toc": True, "component": "loading"},
    {"key": "reference_load", "number": "1.12", "title": "Referenzbeladung Sterilisator bei Validierung", "toc": True, "component": "static"},
    {"key": "equipment_root", "number": "2", "title": "Eingesetzte Prüfmittel", "toc": True, "component": "static"},
    {"key": "measurement_devices", "number": "2.1", "title": "Beschreibung der Messgeräte", "toc": True, "component": "equipment"},
    {"key": "thermo_equipment", "number": "2.2", "title": "Prüfmittel zur thermoelektrischen Untersuchung", "toc": True, "component": "static"},
    {"key": "configuration", "number": "2.3", "title": "Prüfkonfiguration", "toc": True, "component": "static"},
    {"key": "lq", "number": "3", "title": "Leistungsqualifikation (LQ)", "toc": True, "component": "static"},
    {"key": "thermo_tests", "number": "3.1", "title": "Leistungsbeurteilung – Thermoelektrische Prüfungen", "toc": True, "component": "static"},
    {"key": "vacuum_test", "number": "3.1.1", "title": "Vakuumtest", "toc": True, "component": "measurements"},
    {"key": "run_1", "number": "3.2", "title": "Standardbeladung (1. Durchlauf)", "toc": True, "component": "static"},
    {"key": "test_1", "number": "3.2.1", "title": "Test 1", "toc": True, "component": "static"},
    {"key": "run_2", "number": "3.3", "title": "Standardbeladung (2. Durchlauf)", "toc": True, "component": "static"},
    {"key": "test_2", "number": "3.3.1", "title": "Test 2", "toc": True, "component": "static"},
    {"key": "run_3", "number": "3.4", "title": "Standardbeladung (3. Durchlauf)", "toc": True, "component": "static"},
    {"key": "test_3", "number": "3.4.1", "title": "Test 3", "toc": True, "component": "static"},
    {"key": "results", "number": "4", "title": "Ergebnisse der Validierung", "toc": True, "component": "static"},
    {"key": "results_vacuum", "number": "4.1", "title": "Vakuumtest", "toc": True, "component": "static"},
    {"key": "results_runs", "number": "4.1.2", "title": "Testläufe 1 bis 3 Programm 134 °C B", "toc": True, "component": "static"},
    {"key": "drying", "number": None, "title": "Nachweis der Trocknungseigenschaften Testläufe 1 bis 3", "toc": True, "component": "drying"},
    {"key": "recommendations", "number": "4.2", "title": "Empfehlungen und Auflagen", "toc": True, "component": "recommendations"},
    {"key": "appendix", "number": "5", "title": "Anhang", "toc": True, "component": "static"},
    {"key": "winlog", "number": "5.1", "title": "Vakuumtest / Winlog-Auswertungen", "toc": True, "component": "attachments"},
    {"key": "bd_empty_chamber", "number": None, "title": "Testlauf Leerkammerprofil / Bowie-Dick", "toc": True, "component": "static"},
    {"key": "batch_release_docs", "number": None, "title": "Chargen- und Freigabedokumentation", "toc": True, "component": "static"},
    {"key": "release_docs", "number": "5.2", "title": "Freigabedokumentation / Routineprüfungen", "toc": True, "component": "static"},
    {"key": "indicators", "number": "5.3", "title": "Nachweis der umgeschlagenen Indikatoren", "toc": True, "component": "static"},
    {"key": "cycles", "number": "6", "title": "Programmabläufe / Zyklen des Sterilisators", "toc": True, "component": "static"},
    {"key": "practice_certificates", "number": "6.1", "title": "Zertifikate Praxis", "toc": True, "component": "static"},
    {"key": "risk", "number": "7", "title": "Risikoeinstufung der Praxis nach RKI", "toc": True, "component": "static"},
    {"key": "closing_talk", "number": "7.1", "title": "Abschlussgespräch zur Validierung", "toc": True, "component": "static"},
    {"key": "certificates", "number": "8", "title": "Zertifikate", "toc": True, "component": "static"},
    {"key": "calibration_certificates", "number": "9", "title": "Werkskalibrierzertifikate Sensoren", "toc": True, "component": "static"},
]

TEXT_BLOCK_HEADINGS = {
    "summary": "Zusammenfassendes Ergebnis der Validierung",
    "bq_goal": "Anlass und Ziel der Prüfung",
    "legal": "Gesetzliche Grundlagen",
    "performance": "Leistungsüberprüfung",
}

CHECKLIST_DEFINITIONS = [
    (
        "sterilizer_description",
        "Beschreibung Sterilisator",
        ["Beschreibung", "Ja", "Nein", "Nicht anwendbar", "Kommentar"],
        [
            "Trennung von Controlling und Monitoring Schaltkreisen",
            "Temperaturüberwachung Sterilisationsprozess",
            "Drucküberwachung Sterilisationsprozess",
            "Wasserzulauf - Ablauf / manuelle Überwachung",
            "Automatische Aufzeichnung der Sterilisationsergebnisse",
            "Überwachung Wasserqualität",
            "Separater Netzanschluss",
            "Sichtkontrolle: Kessel, Sensoren, Türbereich",
        ],
    ),
    (
        "documentation_control",
        "Dokumentation / Kontrolle",
        ["Vorliegende Dokumente / Beschreibungen", "vorhanden", "eingesehen", "Kommentar"],
        [
            "Bedienungshandbuch / Installationsprotokoll",
            "Wartungshandbuch",
            "Arbeitsanweisungen / Checklisten / reale Beladungsmuster",
            "Schulungsnachweise Mitarbeiter zur Aufbereitung",
            "Risikoeinstufung der Medizinprodukte nach RKI",
            "Protokoll mit Freigabe der Sterilisation durch Mitarbeiter",
            "Chargenkontrolle / Indikator für jede Charge",
            "Chargendokumentation der letzten 6 Wochen",
            "Dokumentation / Bilder der schwierigsten repräsentativen Beladung",
        ],
    ),
    (
        "work_instructions",
        "Arbeitsanweisungen",
        ["Arbeitsanweisung", "vorhanden", "eingesehen", "Prüfintervall", "Kommentar"],
        [
            "Aufbereitung von Medizinprodukten",
            "Beladung Sterilisator",
            "Routinekontrollen",
            "Freigabe von Chargen",
            "Umgang mit Abweichungen",
        ],
    ),
    (
        "environment_conditions",
        "Umgebungsbedingungen",
        ["Prüfpunkt", "Ja", "Nein", "Nicht anwendbar", "Kommentar"],
        ["Raumbedingungen stabil", "Aufstellort frei zugänglich", "Medienversorgung verfügbar"],
    ),
    (
        "batch_documentation",
        "Chargendokumentation",
        ["Prüfpunkt", "vorhanden", "eingesehen", "Kommentar"],
        ["Vakuumtest", "Testlauf 1", "Testlauf 2", "Testlauf 3", "Freigabedokumentation"],
    ),
    (
        "batch_control",
        "Chargenkontrolle",
        ["Prüfpunkt", "Ja", "Nein", "Prüfkörper", "Kommentar"],
        ["Bowie-Dick / Leerkammerprofil", "Helix-Test", "Indikatoren umgeschlagen"],
    ),
]


@dataclass(frozen=True)
class TemplateBundle:
    template: ReportTemplate
    sections: list[ReportSection]
    text_blocks: dict[str, TextBlock]
    checklists: list[ChecklistTemplate]


class ReportTemplateService:
    def __init__(self, session: Session, project_root: Path | None = None) -> None:
        self.session = session
        self.project_root = project_root or self._discover_project_root()

    @property
    def reference_path(self) -> Path:
        return self.project_root / REFERENCE_PATH

    def _discover_project_root(self) -> Path:
        current = Path(__file__).resolve()
        for parent in current.parents:
            if (parent / REFERENCE_PATH).exists():
                return parent
        return Path("/app")

    def ensure_default_template(self) -> TemplateBundle:
        if not self.reference_path.exists():
            raise FileNotFoundError(f"Required reference report is missing: {self.reference_path}")
        template = self.session.scalar(
            select(ReportTemplate).where(ReportTemplate.template_key == TEMPLATE_KEY)
        )
        if template is None:
            template = ReportTemplate(
                template_key=TEMPLATE_KEY,
                name=TEMPLATE_NAME,
                version=TEMPLATE_VERSION,
                validation_type="Erstvalidierung",
                reference_path=REFERENCE_PATH,
                active=True,
            )
            self.session.add(template)
            try:
                self.session.flush()
            except IntegrityError:
                self.session.rollback()
                template = self.session.scalar(
                    select(ReportTemplate).where(ReportTemplate.template_key == TEMPLATE_KEY)
                )
                if template is None:
                    raise
        if not self._has_children(template):
            self._create_children(template)
            self.session.flush()
        return self.load_bundle()

    def load_bundle(self) -> TemplateBundle:
        template = self.session.scalar(
            select(ReportTemplate).where(ReportTemplate.template_key == TEMPLATE_KEY)
        )
        if template is None:
            return self.ensure_default_template()
        sections = list(
            self.session.scalars(
                select(ReportSection)
                .where(ReportSection.template_id == template.id, ReportSection.active.is_(True))
                .order_by(ReportSection.order_index)
            )
        )
        text_blocks = {
            block.block_key: block
            for block in self.session.scalars(
                select(TextBlock)
                .where(TextBlock.template_id == template.id, TextBlock.active.is_(True))
                .order_by(TextBlock.order_index)
            )
        }
        checklists = list(
            self.session.scalars(
                select(ChecklistTemplate)
                .where(ChecklistTemplate.template_id == template.id, ChecklistTemplate.active.is_(True))
                .order_by(ChecklistTemplate.order_index)
            )
        )
        return TemplateBundle(template=template, sections=sections, text_blocks=text_blocks, checklists=checklists)

    def render_block(self, block_key: str, context: Any) -> str:
        bundle = self.load_bundle()
        block = bundle.text_blocks.get(block_key)
        if block is None:
            return "nicht erfasst"
        return self.render_text(block.content, context)

    def render_text(self, content: str, context: Any) -> str:
        values = {
            "device.manufacturer": context.device.manufacturer if context.device else None,
            "device.model": context.device.model if context.device else None,
            "device.serial_number": context.device.serial_number if context.device else None,
            "customer.name": context.customer.name,
            "location.city": context.location.city if context.location else None,
            "validation.performed_on": context.validation.performed_on,
            "validation.next_validation_on": context.validation.next_validation_on,
            "validation.result": context.validation.result,
        }
        rendered = content
        for key, value in values.items():
            rendered = rendered.replace("{{ " + key + " }}", text(value))
        return rendered

    def _has_children(self, template: ReportTemplate) -> bool:
        return bool(
            self.session.scalar(
                select(TextBlock.id).where(TextBlock.template_id == template.id).limit(1)
            )
        )

    def _create_children(self, template: ReportTemplate) -> None:
        paragraphs = self._read_docx_paragraphs()
        blocks = self._extract_blocks(paragraphs)
        for index, section in enumerate(REPORT_SECTIONS, start=1):
            self.session.add(
                ReportSection(
                    template_id=template.id,
                    section_key=str(section["key"]),
                    number=section["number"],
                    title=str(section["title"]),
                    order_index=index,
                    page_break_before=section["number"] in {"1", "2", "3", "4", "5", "6", "7", "8", "9"},
                    active=True,
                )
            )
        for index, (block_key, title) in enumerate(TEXT_BLOCK_HEADINGS.items(), start=1):
            self.session.add(
                TextBlock(
                    template_id=template.id,
                    block_key=block_key,
                    title=title,
                    content=self._sanitize_reference_text(blocks.get(block_key, "nicht erfasst")),
                    order_index=index,
                    version=TEMPLATE_VERSION,
                    active=True,
                )
            )
        for index, (key, title, columns, items) in enumerate(CHECKLIST_DEFINITIONS, start=1):
            self.session.add(
                ChecklistTemplate(
                    template_id=template.id,
                    checklist_key=key,
                    title=title,
                    columns=columns,
                    items=[
                        {"number": item_index + 1, "text": item, "value": "na", "comment": ""}
                        for item_index, item in enumerate(items)
                    ],
                    order_index=index,
                    active=True,
                )
            )

    def _read_docx_paragraphs(self) -> list[str]:
        ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
        with ZipFile(self.reference_path) as archive:
            root = ET.fromstring(archive.read("word/document.xml"))
        paragraphs: list[str] = []
        for paragraph in root.findall(".//w:p", ns):
            line = "".join(
                node.text or "" for node in paragraph.findall(".//w:t", ns)
            ).strip()
            if line:
                paragraphs.append(line)
        return paragraphs

    def _extract_blocks(self, paragraphs: list[str]) -> dict[str, str]:
        blocks: dict[str, str] = {}
        heading_by_normalized = {self._normalize(value): key for key, value in TEXT_BLOCK_HEADINGS.items()}
        active_key: str | None = None
        buffer: list[str] = []
        for paragraph in paragraphs:
            normalized = self._normalize(paragraph)
            if normalized in heading_by_normalized:
                if active_key:
                    blocks[active_key] = "\n\n".join(buffer).strip()
                active_key = heading_by_normalized[normalized]
                buffer = []
                continue
            if active_key and self._looks_like_next_section(paragraph):
                blocks[active_key] = "\n\n".join(buffer).strip()
                active_key = None
                buffer = []
                continue
            if active_key:
                buffer.append(paragraph)
        if active_key:
            blocks[active_key] = "\n\n".join(buffer).strip()
        return blocks

    def _sanitize_reference_text(self, content: str) -> str:
        replacements = {
            "Euronda E10.7": "{{ device.manufacturer }} {{ device.model }}",
            "Euronda / E10.7": "{{ device.manufacturer }} / {{ device.model }}",
            "EXN250688": "{{ device.serial_number }}",
            "Dr.Durmaz": "{{ customer.name }}",
            "Nürnberg": "{{ location.city }}",
            "05.12.2025": "{{ validation.performed_on }}",
            "November 2027": "{{ validation.next_validation_on }}",
            "bestanden": "{{ validation.result }}",
        }
        sanitized = content
        for old, new in replacements.items():
            sanitized = sanitized.replace(old, new)
        return sanitized or "nicht erfasst"

    def _normalize(self, value: str) -> str:
        return re.sub(r"[^a-z0-9]+", "", value.lower())

    def _looks_like_next_section(self, value: str) -> bool:
        if value in {"Inhaltsverzeichnis", "1.3 Angaben zum Gerät"}:
            return True
        return bool(re.match(r"^\d+(\.\d+)*\s", value))
