from __future__ import annotations

from dataclasses import dataclass, field
from datetime import date
from pathlib import Path
import re
from typing import Any

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.contact import Contact
from app.models.customer import Customer, CustomerType
from app.models.device import Device
from app.models.equipment import Equipment, EquipmentKind, EquipmentStatus
from app.models.location import Location
from app.models.validation import Validation, ValidationStatus


@dataclass
class ImportResult:
    created: list[str] = field(default_factory=list)
    updated: list[str] = field(default_factory=list)
    unchanged: list[str] = field(default_factory=list)
    conflicts: list[str] = field(default_factory=list)
    errors: list[str] = field(default_factory=list)
    validation_id: str | None = None

    def as_dict(self) -> dict[str, Any]:
        return {
            "created": self.created,
            "updated": self.updated,
            "unchanged": self.unchanged,
            "conflicts": self.conflicts,
            "errors": self.errors,
            "validation_id": self.validation_id,
        }


class ReferenceMasterdataImportService:
    def __init__(self, session: Session) -> None:
        self.session = session

    def import_reference_docx(
        self,
        reference_path: Path,
        *,
        dry_run: bool = False,
        update_existing: bool = False,
        create_validation: bool = False,
    ) -> ImportResult:
        if not reference_path.exists():
            raise FileNotFoundError(reference_path)

        result = ImportResult()
        payload = self._payload()

        customer = self._upsert_customer(payload["customer"], result, dry_run, update_existing)
        location = self._upsert_location(customer, payload["location"], result, dry_run, update_existing)
        self._upsert_contacts(customer, payload["contacts"], result, dry_run, update_existing)
        device = self._upsert_device(customer, location, payload["device"], result, dry_run, update_existing)
        self._upsert_equipment(payload["equipment"], result, dry_run, update_existing)

        if create_validation:
            validation = self._create_validation(customer, location, device, result, dry_run)
            result.validation_id = validation.id if validation is not None else None

        if not dry_run:
            self.session.commit()

        return result

    def _payload(self) -> dict[str, Any]:
        return {
            "customer": {
                "name": "Urologische Praxis Dr. Durmaz",
                "customer_type": CustomerType.practice,
                "specialty": "Urologie",
                "display_name": "Urologie Dr. Durmaz",
            },
            "location": {
                "name": "Praxis Nürnberg",
                "street": "Wölckernstr. 5",
                "postal_code": "90459",
                "city": "Nürnberg",
                "country": "Deutschland",
            },
            "contacts": [
                {
                    "full_name": "Dr. Durmaz",
                    "function": "Verantwortlicher Betreiber",
                    "notes": "Arzt; QM-Mitverantwortlicher",
                },
                {
                    "full_name": "Frau Bal",
                    "function": "QM-Beauftragte",
                    "notes": "Hygienebeauftragte; Sachkunde A / Fachkenntnisse Aufbereitung und Freigabe von Medizinprodukten",
                },
            ],
            "device": {
                "manufacturer": "Euronda SpA",
                "model": "E10.7",
                "device_type": "Dampf-Kleinsterilisator Klasse B",
                "serial_number": "EXN250688",
                "year_built": 2025,
                "commissioned_on": date(2025, 12, 5),
                "chamber_volume_liters": 23,
                "steam_generation": "Eigendampferzeugung",
                "water_treatment": "Wasserversorgung über Aquafilter Euronda",
                "documentation": "interne CF-Card; Protokollausgabe am PC/Rechner möglich",
                "supplier": "schubamed-Medizintechnik, 92421 Schwandorf",
                "notes": "Sterilisationsverfahren: Konditionierung mit Wasserdampf; Konditionierung teilweise oberhalb und unterhalb des Umgebungsdruckes; Chargendokumentation über interne CF-Card.",
            },
            "equipment": [
                {
                    "kind": EquipmentKind.temperature_logger,
                    "manufacturer": "Ebro",
                    "model": "EBI 11",
                    "serial_number": "15102807",
                    "calibrated_on": date(2025, 1, 17),
                    "status": EquipmentStatus.green,
                    "notes": "Bezeichnung T235; Messbereich 0 °C bis +150 °C",
                },
                {
                    "kind": EquipmentKind.temperature_logger,
                    "manufacturer": "Ebro",
                    "model": "EBI 11",
                    "serial_number": "15211066",
                    "calibrated_on": date(2025, 1, 17),
                    "status": EquipmentStatus.green,
                    "notes": "Bezeichnung T240; Messbereich 0 °C bis +150 °C",
                },
                {
                    "kind": EquipmentKind.temperature_logger,
                    "manufacturer": "Ebro",
                    "model": "EBI 11",
                    "serial_number": "15102538",
                    "calibrated_on": date(2025, 1, 17),
                    "status": EquipmentStatus.green,
                    "notes": "Bezeichnung T240; Messbereich 0 °C bis +150 °C",
                },
                {
                    "kind": EquipmentKind.temperature_logger,
                    "manufacturer": "Ebro",
                    "model": "EBI 11",
                    "serial_number": "15211067",
                    "calibrated_on": date(2025, 1, 17),
                    "status": EquipmentStatus.green,
                    "notes": "Bezeichnung T240; Messbereich 0 °C bis +150 °C",
                },
                {
                    "kind": EquipmentKind.temperature_logger,
                    "manufacturer": "Ebro",
                    "model": "EBI 11",
                    "serial_number": "15125738",
                    "calibrated_on": date(2025, 1, 17),
                    "status": EquipmentStatus.green,
                    "notes": "Bezeichnung T240; Quellenkonflikt: Referenz nennt auch 1525738, mehrfach belegt ist 15125738.",
                },
                {
                    "kind": EquipmentKind.pressure_logger,
                    "manufacturer": "Ebro",
                    "model": "EBI 11",
                    "serial_number": "P111",
                    "calibrated_on": date(2025, 1, 17),
                    "status": EquipmentStatus.green,
                    "notes": "Drucklogger",
                },
            ],
        }

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

    def _upsert_customer(self, payload: dict[str, Any], result: ImportResult, dry_run: bool, update_existing: bool) -> Customer:
        target_name = self._normalize(payload["name"])
        customer = next(
            (item for item in self.session.scalars(select(Customer)) if self._normalize(item.name) == target_name),
            None,
        )
        if customer is None:
            customer = Customer(customer_type=payload["customer_type"], name=payload["name"])
            customer.notes = f"Fachrichtung: {payload['specialty']}\nAnzeigename: {payload['display_name']}"
            if not dry_run:
                self.session.add(customer)
                self.session.flush()
            result.created.append("customer")
            return customer
        if update_existing:
            changed = False
            if customer.notes != f"Fachrichtung: {payload['specialty']}\nAnzeigename: {payload['display_name']}":
                customer.notes = f"Fachrichtung: {payload['specialty']}\nAnzeigename: {payload['display_name']}"
                changed = True
            if customer.customer_type != payload["customer_type"]:
                customer.customer_type = payload["customer_type"]
                changed = True
            if changed:
                result.updated.append("customer")
            else:
                result.unchanged.append("customer")
        else:
            result.unchanged.append("customer")
        return customer

    def _upsert_location(self, customer: Customer, payload: dict[str, Any], result: ImportResult, dry_run: bool, update_existing: bool) -> Location:
        location = next(
            (
                item
                for item in self.session.scalars(select(Location).where(Location.customer_id == customer.id))
                if item.street == payload["street"]
                and item.postal_code == payload["postal_code"]
                and item.city == payload["city"]
            ),
            None,
        )
        if location is None:
            location = Location(
                customer_id=customer.id,
                name=payload["name"],
                street=payload["street"],
                postal_code=payload["postal_code"],
                city=payload["city"],
            )
            if not dry_run:
                self.session.add(location)
                self.session.flush()
            result.created.append("location")
            return location
        if update_existing:
            location.name = payload["name"]
            result.updated.append("location")
        else:
            result.unchanged.append("location")
        return location

    def _upsert_contacts(
        self,
        customer: Customer,
        contacts: list[dict[str, Any]],
        result: ImportResult,
        dry_run: bool,
        update_existing: bool,
    ) -> None:
        for payload in contacts:
            contact = self.session.scalar(
                select(Contact).where(
                    Contact.customer_id == customer.id,
                    Contact.full_name == payload["full_name"],
                )
            )
            notes = payload["notes"]
            if contact is None:
                contact = Contact(
                    customer_id=customer.id,
                    full_name=payload["full_name"],
                    function=payload["function"],
                    notes=notes,
                )
                if not dry_run:
                    self.session.add(contact)
                    self.session.flush()
                result.created.append(f"contact:{payload['full_name']}")
                continue
            if update_existing:
                contact.function = payload["function"]
                contact.notes = notes
                result.updated.append(f"contact:{payload['full_name']}")
            else:
                result.unchanged.append(f"contact:{payload['full_name']}")

    def _upsert_device(
        self,
        customer: Customer,
        location: Location,
        payload: dict[str, Any],
        result: ImportResult,
        dry_run: bool,
        update_existing: bool,
    ) -> Device:
        device = self.session.scalar(select(Device).where(Device.serial_number == payload["serial_number"]))
        if device is None:
            device = Device(
                customer_id=customer.id,
                location_id=location.id,
                manufacturer=payload["manufacturer"],
                model=payload["model"],
                device_type=payload["device_type"],
                serial_number=payload["serial_number"],
                year_built=payload["year_built"],
                commissioned_on=payload["commissioned_on"],
                chamber_volume_liters=payload["chamber_volume_liters"],
                steam_generation=payload["steam_generation"],
                water_treatment=payload["water_treatment"],
                documentation=payload["documentation"],
                supplier=payload["supplier"],
                notes=payload["notes"],
            )
            if not dry_run:
                self.session.add(device)
                self.session.flush()
            result.created.append("device")
            return device
        if update_existing:
            for key, value in payload.items():
                if hasattr(device, key):
                    setattr(device, key, value)
            result.updated.append("device")
        else:
            result.unchanged.append("device")
        return device

    def _upsert_equipment(
        self,
        items: list[dict[str, Any]],
        result: ImportResult,
        dry_run: bool,
        update_existing: bool,
    ) -> None:
        for payload in items:
            equipment = self.session.scalar(
                select(Equipment).where(Equipment.serial_number == payload["serial_number"])
            )
            if equipment is None:
                equipment = Equipment(
                    kind=payload["kind"],
                    manufacturer=payload["manufacturer"],
                    model=payload["model"],
                    serial_number=payload["serial_number"],
                    calibrated_on=payload["calibrated_on"],
                    status=payload["status"],
                    notes=payload["notes"],
                )
                if not dry_run:
                    self.session.add(equipment)
                    self.session.flush()
                result.created.append(f"equipment:{payload['serial_number']}")
                continue
            if update_existing:
                equipment.kind = payload["kind"]
                equipment.manufacturer = payload["manufacturer"]
                equipment.model = payload["model"]
                equipment.calibrated_on = payload["calibrated_on"]
                equipment.status = payload["status"]
                equipment.notes = payload["notes"]
                result.updated.append(f"equipment:{payload['serial_number']}")
            else:
                result.unchanged.append(f"equipment:{payload['serial_number']}")

    def _create_validation(
        self,
        customer: Customer,
        location: Location,
        device: Device,
        result: ImportResult,
        dry_run: bool,
    ) -> Validation | None:
        existing = self.session.scalar(
            select(Validation).where(
                Validation.customer_id == customer.id,
                Validation.device_id == device.id,
                Validation.validation_type == "Erstvalidierung",
            )
        )
        if existing is not None:
            result.unchanged.append("validation")
            return existing
        validation = Validation(
            report_number="REF-IMPORT-1",
            customer_id=customer.id,
            location_id=location.id,
            device_id=device.id,
            validation_type="Erstvalidierung",
            status=ValidationStatus.draft.value,
            examiner_name="nicht erfasst",
        )
        if not dry_run:
            self.session.add(validation)
            self.session.flush()
        result.created.append("validation")
        return validation
