from __future__ import annotations

import csv
import io
from dataclasses import dataclass
from datetime import date, datetime
from uuid import uuid4

from dateutil.relativedelta import relativedelta
from sqlalchemy import and_, or_, select
from sqlalchemy.orm import Session

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

REQUIRED_FIELDS = {
    "report_number": ("Allgemeine Angaben", "Berichtsnummer"),
    "validation_type": ("Allgemeine Angaben", "Validierungsart"),
    "performed_on": ("Allgemeine Angaben", "Pruefdatum"),
    "customer_id": ("Kunde und Standort", "Kunde"),
    "location_id": ("Kunde und Standort", "Standort"),
    "device_id": ("Geraet", "Geraet"),
    "examiner_name": ("Allgemeine Angaben", "Pruefer"),
}


@dataclass(frozen=True)
class ReviewIssue:
    field: str
    message: str
    section: str

    def as_dict(self) -> dict[str, str]:
        return {"field": self.field, "message": self.message, "section": self.section}


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

    def review(self, validation: Validation) -> dict:
        errors = self._required_errors(validation) + self._reference_errors(validation)
        warnings = self._warnings(validation)
        complete_sections = self._complete_sections(validation, errors, warnings)
        return {
            "status": validation.status,
            "errors": [issue.as_dict() for issue in errors],
            "warnings": [issue.as_dict() for issue in warnings],
            "complete_sections": complete_sections,
        }

    def mark_ready_for_review(self, validation: Validation) -> dict:
        review = self.review(validation)
        validation.status = (
            ValidationStatus.ready_for_review.value
            if not review["errors"]
            else ValidationStatus.draft.value
        )
        self.session.flush()
        review["status"] = validation.status
        return review

    def apply_revalidation_date(self, validation: Validation) -> None:
        if validation.performed_on and not validation.next_validation_manually_overridden:
            validation.next_validation_on = validation.performed_on + relativedelta(
                months=validation.revalidation_interval_months or 24
            )

    def duplicate(self, validation: Validation) -> Validation:
        clone = Validation(
            report_number=f"{validation.report_number}-KOPIE-{str(uuid4())[:8]}",
            customer_id=validation.customer_id,
            location_id=validation.location_id,
            contact_id=validation.contact_id,
            device_id=validation.device_id,
            validation_type=validation.validation_type,
            project=validation.project,
            test_location=validation.test_location,
            examiner_name=validation.examiner_name,
            participants=validation.participants,
            operator_name=validation.operator_name,
            scheduled_on=validation.scheduled_on,
            performed_on=validation.performed_on,
            next_validation_on=validation.next_validation_on,
            revalidation_interval_months=validation.revalidation_interval_months,
            next_validation_manually_overridden=validation.next_validation_manually_overridden,
            version=validation.version + 1,
            previous_validation_id=validation.id,
            examiner_id=validation.examiner_id,
            status=ValidationStatus.draft.value,
            result=validation.result,
            notes=validation.notes,
            equipment_ids=validation.equipment_ids,
            environment_conditions=validation.environment_conditions,
            documentation_checklist=validation.documentation_checklist,
            performance_checklist=validation.performance_checklist,
            programs=validation.programs,
            loading_patterns=validation.loading_patterns,
            measurement_data=validation.measurement_data,
            drying=validation.drying,
            recommendations=validation.recommendations,
            attachments=validation.attachments,
        )
        self.session.add(clone)
        self.session.flush()
        return clone

    def create_new_version(self, validation: Validation) -> Validation:
        clone = self.duplicate(validation)
        clone.report_number = f"{validation.report_number}-V{clone.version}"
        return clone

    def export_json(self, validation: Validation) -> dict:
        return {
            column.name: getattr(validation, column.name)
            for column in Validation.__table__.columns
            if column.name not in {"created_at", "updated_at"}
        }

    def preview_csv(self, content: bytes) -> dict:
        rows = []
        reader = csv.DictReader(io.StringIO(content.decode("utf-8-sig")))
        for index, row in enumerate(reader, start=2):
            rows.append(self._preview_import_row(index, row))
        return {
            "rows": rows,
            "valid_rows": sum(1 for row in rows if not row["errors"]),
            "invalid_rows": sum(1 for row in rows if row["errors"]),
            "duplicates": sum(1 for row in rows if row["duplicate"]),
        }

    def import_rows(self, rows: list[dict], duplicate_strategy: str) -> dict:
        summary = {"successful": 0, "skipped": 0, "failed": 0, "errors": []}
        for index, row in enumerate(rows, start=1):
            preview = self._preview_import_row(index, row)
            if preview["errors"]:
                summary["failed"] += 1
                summary["errors"].append(f"Zeile {index}: {', '.join(preview['errors'])}")
                continue
            existing = self.session.scalar(
                select(Validation).where(Validation.report_number == row.get("report_number"))
            )
            if existing and duplicate_strategy == "skip":
                summary["skipped"] += 1
                continue
            target = existing if existing and duplicate_strategy == "update" else Validation()
            target.report_number = (
                f"{row.get('report_number')}-IMPORT-{str(uuid4())[:8]}"
                if existing and duplicate_strategy == "copy"
                else row.get("report_number")
            )
            target.validation_type = row.get("validation_type")
            target.performed_on = self._parse_date(row.get("test_date") or row.get("performed_on"))
            target.customer_id = preview["resolved_customer_id"]
            target.location_id = preview["resolved_location_id"]
            target.contact_id = row.get("contact_id")
            target.device_id = preview["resolved_device_id"]
            target.examiner_name = row.get("examiner") or row.get("examiner_name")
            target.project = row.get("project")
            target.test_location = row.get("test_location")
            target.participants = row.get("participants")
            target.operator_name = row.get("operator_name")
            target.next_validation_on = self._parse_date(row.get("next_validation_on"))
            target.equipment_ids = row.get("equipment_ids") or []
            target.environment_conditions = row.get("environment_conditions") or {}
            target.documentation_checklist = row.get("documentation_checklist") or []
            target.performance_checklist = row.get("performance_checklist") or []
            target.programs = row.get("programs") or []
            target.loading_patterns = row.get("loading_patterns") or []
            target.measurement_data = row.get("measurement_data") or []
            target.drying = row.get("drying") or {}
            target.recommendations = row.get("recommendations") or []
            target.attachments = row.get("attachments") or []
            target.status = row.get("status") or ValidationStatus.draft.value
            target.result = row.get("result")
            target.notes = row.get("notes")
            if target.id is None:
                self.session.add(target)
            summary["successful"] += 1
        self.session.flush()
        return summary

    def query_validations(
        self,
        search: str | None,
        page: int,
        page_size: int,
        sort_by: str,
        sort_order: str,
        filters: dict,
    ) -> dict:
        statement = select(Validation)
        count_statement = select(Validation)
        conditions = []
        if search:
            term = f"%{search}%"
            conditions.append(
                or_(
                    Validation.report_number.ilike(term),
                    Validation.validation_type.ilike(term),
                    Validation.examiner_name.ilike(term),
                    Validation.result.ilike(term),
                )
            )
        for key in ["status", "customer_id", "device_id", "validation_type", "result"]:
            if filters.get(key):
                conditions.append(getattr(Validation, key) == filters[key])
        if filters.get("date_from"):
            conditions.append(Validation.performed_on >= filters["date_from"])
        if filters.get("date_to"):
            conditions.append(Validation.performed_on <= filters["date_to"])
        if filters.get("overdue_only"):
            conditions.append(Validation.next_validation_on < date.today())
            conditions.append(Validation.status != ValidationStatus.cancelled.value)
        if conditions:
            statement = statement.where(and_(*conditions))
            count_statement = count_statement.where(and_(*conditions))
        sort_column = getattr(Validation, sort_by, Validation.updated_at)
        if sort_order == "asc":
            statement = statement.order_by(sort_column.asc())
        else:
            statement = statement.order_by(sort_column.desc())
        total = len(list(self.session.scalars(count_statement)))
        items = list(self.session.scalars(statement.offset((page - 1) * page_size).limit(page_size)))
        return {"items": items, "total": total, "page": page, "page_size": page_size}

    def revalidation_status(self, validation: Validation) -> str:
        if not validation.next_validation_on:
            return "nicht_erfasst"
        delta = (validation.next_validation_on - date.today()).days
        if delta < 0:
            return "ueberfaellig"
        if delta <= 30:
            return "faellig_30"
        if delta <= 90:
            return "faellig_90"
        return "faellig_spaeter"

    def _required_errors(self, validation: Validation) -> list[ReviewIssue]:
        issues = []
        for field, (section, label) in REQUIRED_FIELDS.items():
            if not getattr(validation, field):
                issues.append(ReviewIssue(field, f"{label} fehlt.", section))
        return issues

    def _reference_errors(self, validation: Validation) -> list[ReviewIssue]:
        issues = []
        customer = self.session.get(Customer, validation.customer_id) if validation.customer_id else None
        location = self.session.get(Location, validation.location_id) if validation.location_id else None
        device = self.session.get(Device, validation.device_id) if validation.device_id else None
        if validation.customer_id and customer is None:
            issues.append(ReviewIssue("customer_id", "Kunde existiert nicht.", "Kunde und Standort"))
        if validation.location_id and location is None:
            issues.append(ReviewIssue("location_id", "Standort existiert nicht.", "Kunde und Standort"))
        if validation.device_id and device is None:
            issues.append(ReviewIssue("device_id", "Geraet existiert nicht.", "Geraet"))
        if location and device and device.location_id != location.id:
            issues.append(ReviewIssue("device_id", "Geraet gehoert nicht zum gewaehlten Standort.", "Geraet"))
        return issues

    def _warnings(self, validation: Validation) -> list[ReviewIssue]:
        warnings = []
        equipment = []
        if validation.equipment_ids:
            equipment = list(self.session.scalars(select(Equipment).where(Equipment.id.in_(validation.equipment_ids))))
        if not validation.equipment_ids:
            warnings.append(ReviewIssue("equipment_ids", "Keine Pruefmittel ausgewaehlt.", "Pruefmittel"))
        for item in equipment:
            if item.calibration_due_on and item.calibration_due_on < date.today():
                warnings.append(ReviewIssue("equipment_ids", f"Pruefmittel {item.serial_number} ist abgelaufen.", "Pruefmittel"))
        if not any(item.get("selected") for item in validation.programs or []):
            warnings.append(ReviewIssue("programs", "Keine Programme ausgewaehlt.", "Programme"))
        if not validation.measurement_data:
            warnings.append(ReviewIssue("measurement_data", "Keine Messdaten vorhanden.", "Messdaten"))
        if not validation.attachments:
            warnings.append(ReviewIssue("attachments", "Keine Bilder oder Anlagen vorhanden.", "Bilder und Anlagen"))
        if not validation.recommendations:
            warnings.append(ReviewIssue("recommendations", "Keine Empfehlungen oder Auflagen erfasst.", "Empfehlungen"))
        winlog_imported = any(item.get("imports") for item in validation.measurement_data or [])
        if not winlog_imported:
            warnings.append(ReviewIssue("measurement_data", "Winlog-Datei noch nicht importiert.", "Messdaten"))
        return warnings

    def _complete_sections(
        self, validation: Validation, errors: list[ReviewIssue], warnings: list[ReviewIssue]
    ) -> list[str]:
        blocked = {issue.section for issue in [*errors, *warnings]}
        sections = [
            "Allgemeine Angaben",
            "Kunde und Standort",
            "Geraet",
            "Pruefmittel",
            "Programme",
            "Messdaten",
            "Bilder und Anlagen",
            "Empfehlungen",
        ]
        return [section for section in sections if section not in blocked]

    def _preview_import_row(self, row_number: int, row: dict) -> dict:
        errors = []
        customer = self.session.get(Customer, row.get("customer_id")) if row.get("customer_id") else self.session.scalar(select(Customer).where(Customer.name == row.get("customer_reference")))
        location = self.session.get(Location, row.get("location_id")) if row.get("location_id") else self.session.scalar(select(Location).where(Location.name == row.get("location_reference")))
        device = self.session.get(Device, row.get("device_id")) if row.get("device_id") else self.session.scalar(select(Device).where(Device.serial_number == row.get("device_serial_number")))
        duplicate = bool(
            row.get("report_number")
            and self.session.scalar(select(Validation).where(Validation.report_number == row.get("report_number")))
        )
        required = [
            ("report_number", row.get("report_number")),
            ("validation_type", row.get("validation_type")),
            ("test_date", row.get("test_date") or row.get("performed_on")),
            ("customer_reference", row.get("customer_reference") or row.get("customer_id")),
            ("location_reference", row.get("location_reference") or row.get("location_id")),
            ("device_serial_number", row.get("device_serial_number") or row.get("device_id")),
            ("examiner", row.get("examiner") or row.get("examiner_name")),
        ]
        for field, value in required:
            if not value:
                errors.append(f"{field} fehlt")
        if row.get("customer_reference") and not customer:
            errors.append("Kunde nicht gefunden")
        if row.get("location_reference") and not location:
            errors.append("Standort nicht gefunden")
        if row.get("device_serial_number") and not device:
            errors.append("Geraet nicht gefunden")
        return {
            "row_number": row_number,
            "data": row,
            "errors": errors,
            "duplicate": duplicate,
            "resolved_customer_id": customer.id if customer else None,
            "resolved_location_id": location.id if location else None,
            "resolved_device_id": device.id if device else None,
        }

    def _parse_date(self, value: str | None) -> date | None:
        if not value:
            return None
        return datetime.strptime(value, "%Y-%m-%d").date()
