from __future__ import annotations

from typing import Any

import json
import logging
import shutil
import uuid
from pathlib import Path

from fastapi import APIRouter, Depends, File, Form, Query, Response, UploadFile
from fastapi.encoders import jsonable_encoder
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from sqlalchemy import func, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from app.api.dependencies import current_admin, current_user
from app.db.session import get_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.user import User, UserRole
from app.models.validation import Validation
from app.modules.helios.service import HeliosImportService
from app.modules.orion.service import OrionReportService
from app.schemas.common import PaginatedResponse
from app.schemas.domain import (
    ContactCreate,
    ContactRead,
    ContactUpdate,
    CustomerCreate,
    CustomerRead,
    CustomerUpdate,
    DeviceCreate,
    DeviceRead,
    DeviceUpdate,
    EquipmentCreate,
    EquipmentRead,
    EquipmentUpdate,
    LocationCreate,
    LocationRead,
    LocationUpdate,
    MeasurementImportConfirmRequest,
    MeasurementImportPreviewRead,
    ValidationCreate,
    ValidationImportPreview,
    ValidationImportRequest,
    ValidationImportSummary,
    ValidationRead,
    ValidationReview,
    ValidationUpdate,
    UserCreate,
    UserRead,
    UserUpdate,
    UserPasswordResetRequest,
)
from app.services.domain_service import CrudService, DomainServices
from app.services.validation_workflow import ValidationWorkflowService

router = APIRouter(tags=["domain"], dependencies=[Depends(current_user)])
logger = logging.getLogger(__name__)


@router.get("/report-templates/default/checklists")
def default_report_checklists(session: Session = Depends(get_session)) -> list[dict]:
    from app.modules.orion.template_service import ReportTemplateService

    bundle = ReportTemplateService(session).ensure_default_template()
    session.commit()
    return [
        {
            "checklist_key": item.checklist_key,
            "title": item.title,
            "columns": item.columns,
            "items": item.items,
            "order_index": item.order_index,
        }
        for item in bundle.checklists
    ]


@router.get("/dashboard")
def dashboard(session: Session = Depends(get_session)) -> dict[str, int]:
    today = func.current_date()
    return {
        "customers": session.scalar(select(func.count()).select_from(Customer)) or 0,
        "locations": session.scalar(select(func.count()).select_from(Location)) or 0,
        "contacts": session.scalar(select(func.count()).select_from(Contact)) or 0,
        "devices": session.scalar(select(func.count()).select_from(Device)) or 0,
        "equipment": session.scalar(select(func.count()).select_from(Equipment)) or 0,
        "equipment_green": session.scalar(
            select(func.count()).select_from(Equipment).where(Equipment.status == "green")
        )
        or 0,
        "equipment_yellow": session.scalar(
            select(func.count()).select_from(Equipment).where(Equipment.status == "yellow")
        )
        or 0,
        "equipment_red": session.scalar(
            select(func.count()).select_from(Equipment).where(Equipment.status == "red")
        )
        or 0,
        "validations": session.scalar(select(func.count()).select_from(Validation)) or 0,
        "validation_drafts": session.scalar(
            select(func.count()).select_from(Validation).where(Validation.status == "ENTWURF")
        )
        or 0,
        "validation_ready": session.scalar(
            select(func.count())
            .select_from(Validation)
            .where(Validation.status == "BEREIT_ZUR_PRUEFUNG")
        )
        or 0,
        "validation_in_review": session.scalar(
            select(func.count()).select_from(Validation).where(Validation.status == "IN_PRUEFUNG")
        )
        or 0,
        "validation_approved": session.scalar(
            select(func.count()).select_from(Validation).where(Validation.status == "FREIGEGEBEN")
        )
        or 0,
        "validation_completed": session.scalar(
            select(func.count()).select_from(Validation).where(Validation.status == "ABGESCHLOSSEN")
        )
        or 0,
        "validation_overdue": session.scalar(
            select(func.count())
            .select_from(Validation)
            .where(Validation.next_validation_on < today)
        )
        or 0,
    }


def paging(
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=20, ge=1, le=100),
    search: str | None = Query(default=None, max_length=120),
) -> dict[str, Any]:
    return {"page": page, "page_size": page_size, "search": search}


def commit_create(session: Session, service: CrudService, payload):
    try:
        item = service.create(payload.model_dump())
        if isinstance(item, Validation):
            ValidationWorkflowService(session).apply_revalidation_date(item)
        session.commit()
        session.refresh(item)
        return item
    except IntegrityError as exc:
        session.rollback()
        from fastapi import HTTPException

        raise HTTPException(
            status_code=409, detail="Datensatz verletzt Datenbankbeziehungen."
        ) from exc


def commit_update(session: Session, service: CrudService, item_id: str, payload):
    try:
        item = service.update(item_id, payload.model_dump())
        if isinstance(item, Validation):
            ValidationWorkflowService(session).apply_revalidation_date(item)
        session.commit()
        session.refresh(item)
        return item
    except IntegrityError as exc:
        session.rollback()
        from fastapi import HTTPException

        raise HTTPException(
            status_code=409, detail="Datensatz verletzt Datenbankbeziehungen."
        ) from exc


def commit_delete(session: Session, service: CrudService, item_id: str) -> Response:
    service.delete(item_id)
    session.commit()
    return Response(status_code=204)


@router.get("/customers", response_model=PaginatedResponse[CustomerRead])
def list_customers(params: dict = Depends(paging), session: Session = Depends(get_session)):
    return DomainServices(session).customers.list(**params)


@router.post("/customers", response_model=CustomerRead, status_code=201)
def create_customer(payload: CustomerCreate, session: Session = Depends(get_session)):
    return commit_create(session, DomainServices(session).customers, payload)


@router.put("/customers/{item_id}", response_model=CustomerRead)
def update_customer(item_id: str, payload: CustomerUpdate, session: Session = Depends(get_session)):
    return commit_update(session, DomainServices(session).customers, item_id, payload)


@router.delete("/customers/{item_id}", status_code=204)
def delete_customer(item_id: str, session: Session = Depends(get_session)):
    return commit_delete(session, DomainServices(session).customers, item_id)


@router.get("/locations", response_model=PaginatedResponse[LocationRead])
def list_locations(params: dict = Depends(paging), session: Session = Depends(get_session)):
    return DomainServices(session).locations.list(**params)


@router.post("/locations", response_model=LocationRead, status_code=201)
def create_location(payload: LocationCreate, session: Session = Depends(get_session)):
    return commit_create(session, DomainServices(session).locations, payload)


@router.put("/locations/{item_id}", response_model=LocationRead)
def update_location(item_id: str, payload: LocationUpdate, session: Session = Depends(get_session)):
    return commit_update(session, DomainServices(session).locations, item_id, payload)


@router.delete("/locations/{item_id}", status_code=204)
def delete_location(item_id: str, session: Session = Depends(get_session)):
    return commit_delete(session, DomainServices(session).locations, item_id)


@router.get("/contacts", response_model=PaginatedResponse[ContactRead])
def list_contacts(params: dict = Depends(paging), session: Session = Depends(get_session)):
    return DomainServices(session).contacts.list(**params)


@router.post("/contacts", response_model=ContactRead, status_code=201)
def create_contact(payload: ContactCreate, session: Session = Depends(get_session)):
    return commit_create(session, DomainServices(session).contacts, payload)


@router.put("/contacts/{item_id}", response_model=ContactRead)
def update_contact(item_id: str, payload: ContactUpdate, session: Session = Depends(get_session)):
    return commit_update(session, DomainServices(session).contacts, item_id, payload)


@router.delete("/contacts/{item_id}", status_code=204)
def delete_contact(item_id: str, session: Session = Depends(get_session)):
    return commit_delete(session, DomainServices(session).contacts, item_id)


@router.get("/devices", response_model=PaginatedResponse[DeviceRead])
def list_devices(params: dict = Depends(paging), session: Session = Depends(get_session)):
    return DomainServices(session).devices.list(**params)


@router.post("/devices", response_model=DeviceRead, status_code=201)
def create_device(payload: DeviceCreate, session: Session = Depends(get_session)):
    return commit_create(session, DomainServices(session).devices, payload)


@router.put("/devices/{item_id}", response_model=DeviceRead)
def update_device(item_id: str, payload: DeviceUpdate, session: Session = Depends(get_session)):
    return commit_update(session, DomainServices(session).devices, item_id, payload)


@router.delete("/devices/{item_id}", status_code=204)
def delete_device(item_id: str, session: Session = Depends(get_session)):
    return commit_delete(session, DomainServices(session).devices, item_id)


@router.get("/equipment", response_model=PaginatedResponse[EquipmentRead])
def list_equipment(params: dict = Depends(paging), session: Session = Depends(get_session)):
    return DomainServices(session).equipment.list(**params)


@router.post("/equipment", response_model=EquipmentRead, status_code=201)
def create_equipment(payload: EquipmentCreate, session: Session = Depends(get_session)):
    return commit_create(session, DomainServices(session).equipment, payload)


@router.put("/equipment/{item_id}", response_model=EquipmentRead)
def update_equipment(
    item_id: str, payload: EquipmentUpdate, session: Session = Depends(get_session)
):
    return commit_update(session, DomainServices(session).equipment, item_id, payload)


@router.delete("/equipment/{item_id}", status_code=204)
def delete_equipment(item_id: str, session: Session = Depends(get_session)):
    return commit_delete(session, DomainServices(session).equipment, item_id)


@router.get("/validations", response_model=PaginatedResponse[ValidationRead])
def list_validations(
    search: str | None = Query(default=None, max_length=120),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=20, ge=1, le=100),
    sort_by: str = Query(default="updated_at"),
    sort_order: str = Query(default="desc", pattern="^(asc|desc)$"),
    status: str | None = None,
    customer_id: str | None = None,
    device_id: str | None = None,
    validation_type: str | None = None,
    result: str | None = None,
    date_from: str | None = None,
    date_to: str | None = None,
    overdue_only: bool = False,
    session: Session = Depends(get_session),
):
    return ValidationWorkflowService(session).query_validations(
        search=search,
        page=page,
        page_size=page_size,
        sort_by=sort_by,
        sort_order=sort_order,
        filters={
            "status": status,
            "customer_id": customer_id,
            "device_id": device_id,
            "validation_type": validation_type,
            "result": result,
            "date_from": date_from,
            "date_to": date_to,
            "overdue_only": overdue_only,
        },
    )


@router.get("/validations/next-report-number")
def next_report_number(session: Session = Depends(get_session)) -> dict[str, str]:
    count = session.scalar(select(func.count()).select_from(Validation)) or 0
    return {"report_number": f"VAL-{count + 1:05d}"}


@router.get("/validations/{item_id}", response_model=ValidationRead)
def get_validation(item_id: str, session: Session = Depends(get_session)):
    item = DomainServices(session).validations.repository.get(item_id)
    if item is None:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="Resource not found")
    return item


@router.post("/validations", response_model=ValidationRead, status_code=201)
def create_validation(payload: ValidationCreate, session: Session = Depends(get_session)):
    return commit_create(session, DomainServices(session).validations, payload)


@router.put("/validations/{item_id}", response_model=ValidationRead)
def update_validation(
    item_id: str, payload: ValidationUpdate, session: Session = Depends(get_session)
):
    return commit_update(session, DomainServices(session).validations, item_id, payload)


@router.delete("/validations/{item_id}", status_code=204)
def delete_validation(item_id: str, session: Session = Depends(get_session)):
    item = DomainServices(session).validations.repository.get(item_id)
    if item and item.status != "ENTWURF":
        from fastapi import HTTPException

        raise HTTPException(status_code=409, detail="Only draft validations can be deleted")
    return commit_delete(session, DomainServices(session).validations, item_id)


@router.post("/validations/{item_id}/review", response_model=ValidationReview)
def review_validation(item_id: str, session: Session = Depends(get_session)):
    item = DomainServices(session).validations.repository.get(item_id)
    if item is None:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="Resource not found")
    review = ValidationWorkflowService(session).mark_ready_for_review(item)
    session.commit()
    return review


@router.post("/validations/{item_id}/duplicate", response_model=ValidationRead, status_code=201)
def duplicate_validation(item_id: str, session: Session = Depends(get_session)):
    item = DomainServices(session).validations.repository.get(item_id)
    if item is None:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="Resource not found")
    clone = ValidationWorkflowService(session).duplicate(item)
    session.commit()
    session.refresh(clone)
    return clone


@router.post("/validations/{item_id}/new-version", response_model=ValidationRead, status_code=201)
def new_validation_version(item_id: str, session: Session = Depends(get_session)):
    item = DomainServices(session).validations.repository.get(item_id)
    if item is None:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="Resource not found")
    clone = ValidationWorkflowService(session).create_new_version(item)
    session.commit()
    session.refresh(clone)
    return clone


@router.post("/validations/{item_id}/cancel", response_model=ValidationRead)
def cancel_validation(item_id: str, session: Session = Depends(get_session)):
    item = DomainServices(session).validations.repository.get(item_id)
    if item is None:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="Resource not found")
    item.status = "STORNIERT"
    session.commit()
    session.refresh(item)
    return item


@router.post("/validations/{item_id}/attachments")
def upload_validation_attachment(
    item_id: str,
    category: str = Form(...),
    description: str = Form(default=""),
    order: int = Form(default=0),
    file: UploadFile = File(...),
    session: Session = Depends(get_session),
):
    item = DomainServices(session).validations.repository.get(item_id)
    if item is None:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="Resource not found")
    if item.status in {"FREIGEGEBEN", "ABGESCHLOSSEN"}:
        from fastapi import HTTPException

        raise HTTPException(status_code=409, detail="Freigegebene Berichte sind schreibgeschuetzt.")

    original_name = Path(file.filename or "anlage").name
    suffix = Path(original_name).suffix
    stored_name = f"{uuid.uuid4().hex}{suffix}"
    upload_dir = Path("/app/uploads/validations") / item_id
    upload_dir.mkdir(parents=True, exist_ok=True)
    storage_path = upload_dir / stored_name
    with storage_path.open("wb") as target:
        shutil.copyfileobj(file.file, target)

    attachment = {
        "category": category,
        "filename": original_name,
        "content_type": file.content_type,
        "description": description,
        "order": order,
        "storage_path": str(storage_path),
        "url": f"/uploads/validations/{item_id}/{stored_name}",
    }
    current = list(item.attachments or [])
    current.append(attachment)
    item.attachments = current
    session.commit()
    return attachment


@router.post(
    "/validations/{item_id}/measurement-imports/winlog-pdf",
    response_model=MeasurementImportPreviewRead,
)
async def upload_winlog_pdf_import(
    item_id: str,
    file: UploadFile = File(...),
    attachment_only: bool = Form(default=False),
    session: Session = Depends(get_session),
):
    item = DomainServices(session).validations.repository.get(item_id)
    if item is None:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="Resource not found")
    preview = HeliosImportService(session).save_winlog_pdf(
        item_id,
        file.filename or "winlog.pdf",
        await file.read(),
        attachment_only=attachment_only,
    )
    session.commit()
    return preview


@router.post(
    "/measurement-imports/{import_id}/confirm",
    response_model=MeasurementImportPreviewRead,
)
def confirm_measurement_import(
    import_id: str,
    payload: MeasurementImportConfirmRequest,
    session: Session = Depends(get_session),
):
    try:
        preview = HeliosImportService(session).confirm_values(
            import_id, [item.model_dump() for item in payload.values]
        )
    except ValueError as exc:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail=str(exc)) from exc
    session.commit()
    return preview


@router.get("/validations/{item_id}/export.json")
def export_validation_json(item_id: str, session: Session = Depends(get_session)):
    item = DomainServices(session).validations.repository.get(item_id)
    if item is None:
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="Resource not found")
    data = ValidationWorkflowService(session).export_json(item)
    return JSONResponse(
        content=json.loads(json.dumps(data, default=str)),
        headers={"Content-Disposition": f'attachment; filename="{item.report_number}.json"'},
    )


@router.post("/validations/import/csv-preview", response_model=ValidationImportPreview)
async def preview_validation_csv(file: UploadFile, session: Session = Depends(get_session)):
    return ValidationWorkflowService(session).preview_csv(await file.read())


@router.post("/validations/import/json", response_model=ValidationImportSummary)
def import_validation_json(
    payload: ValidationImportRequest, session: Session = Depends(get_session)
):
    summary = ValidationWorkflowService(session).import_rows(
        payload.rows, payload.duplicate_strategy
    )
    session.commit()
    return summary


@router.get("/validations/{item_id}/report.html", response_class=HTMLResponse)
def validation_report_preview(item_id: str, session: Session = Depends(get_session)):
    return OrionReportService(session, Path("/app/reports")).render_html(item_id)


@router.get("/validations/{item_id}/report.pdf")
def validation_report_pdf(item_id: str, session: Session = Depends(get_session)):
    try:
        report_path = OrionReportService(session, Path("/app/reports")).render_pdf(item_id)
    except Exception as exc:
        logger.exception("PDF generation failed for validation %s", item_id)
        from fastapi import HTTPException

        raise HTTPException(
            status_code=502,
            detail="Der PDF-Bericht konnte nicht erzeugt werden. Die HTML-Vorschau ist weiterhin verfuegbar.",
        ) from exc
    return FileResponse(
        report_path,
        media_type="application/pdf",
        filename=report_path.name,
    )
def _user_payload(user: User) -> dict:
    return {
        "id": user.id,
        "created_at": user.created_at,
        "updated_at": user.updated_at,
        "email": user.email,
        "first_name": user.first_name,
        "last_name": user.last_name,
        "role": user.role,
        "is_active": user.is_active,
        "must_change_password": user.must_change_password,
        "last_login_at": user.last_login_at,
        "password_changed_at": user.password_changed_at,
    }


@router.get("/users", response_model=PaginatedResponse[UserRead])
def list_users(
    session: Session = Depends(get_session),
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    search: str | None = Query(default=None),
    role: str | None = Query(default=None),
    active: bool | None = Query(default=None),
    sort_by: str = Query(default="created_at"),
    sort_order: str = Query(default="desc"),
    _: User = Depends(current_admin),
):
    query = select(User)
    if search:
        term = f"%{search.strip()}%"
        query = query.where(
            (User.first_name.ilike(term))
            | (User.last_name.ilike(term))
            | (User.email.ilike(term))
        )
    if role:
        query = query.where(User.role == role)
    if active is not None:
        query = query.where(User.is_active.is_(active))
    sort_columns = {
        "name": User.last_name,
        "email": User.email,
        "role": User.role,
        "is_active": User.is_active,
        "last_login_at": User.last_login_at,
        "password_changed_at": User.password_changed_at,
        "created_at": User.created_at,
        "updated_at": User.updated_at,
    }
    sort_column = sort_columns.get(sort_by, User.created_at)
    query = query.order_by(sort_column.asc() if sort_order.lower() == "asc" else sort_column.desc())
    total = session.scalar(select(func.count()).select_from(query.order_by(None).subquery())) or 0
    items = list(session.scalars(query.offset((page - 1) * page_size).limit(page_size)))
    return {"items": items, "total": total, "page": page, "page_size": page_size}


@router.post("/users", status_code=201)
def create_user(payload: UserCreate, session: Session = Depends(get_session), _: User = Depends(current_admin)):
    from app.core.security import hash_password
    import secrets
    from app.models.user import User

    temporary_password = payload.temporary_password or secrets.token_urlsafe(12)
    user = User(
        email=payload.email.lower(),
        first_name=payload.first_name,
        last_name=payload.last_name,
        role=payload.role.value,
        password_hash=hash_password(temporary_password),
        is_active=payload.is_active,
        must_change_password=payload.must_change_password,
    )
    session.add(user)
    session.commit()
    session.refresh(user)
    return JSONResponse(content={"temporary_password": temporary_password, "user": jsonable_encoder(user)}, status_code=201)


@router.get("/users/{item_id}", response_model=UserRead)
def get_user(item_id: str, session: Session = Depends(get_session), _: User = Depends(current_admin)):
    user = session.get(User, item_id)
    if user is None:
        raise HTTPException(status_code=404, detail="Resource not found")
    return user


@router.put("/users/{item_id}", response_model=UserRead)
def update_user(item_id: str, payload: UserUpdate, session: Session = Depends(get_session), me: User = Depends(current_admin)):
    user = session.get(User, item_id)
    if user is None:
        raise HTTPException(status_code=404, detail="Resource not found")
    if user.id == me.id and not payload.is_active:
        raise HTTPException(status_code=409, detail="Own admin account cannot be deactivated")
    active_admins = session.scalar(
        select(func.count()).select_from(User).where(User.role == UserRole.ADMIN.value, User.is_active.is_(True))
    ) or 0
    if user.role == UserRole.ADMIN.value and active_admins <= 1 and (not payload.is_active or payload.role != UserRole.ADMIN.value):
        raise HTTPException(status_code=409, detail="Der letzte aktive ADMIN darf nicht deaktiviert oder herabgestuft werden.")
    user.first_name = payload.first_name
    user.last_name = payload.last_name
    user.email = payload.email.lower()
    user.role = payload.role.value
    user.is_active = payload.is_active
    user.must_change_password = payload.must_change_password
    session.commit()
    session.refresh(user)
    return user


@router.post("/users/{item_id}/deactivate", response_model=UserRead)
def deactivate_user(item_id: str, session: Session = Depends(get_session), me: User = Depends(current_admin)):
    user = session.get(User, item_id)
    if user is None:
        raise HTTPException(status_code=404, detail="Resource not found")
    if user.id == me.id:
        raise HTTPException(status_code=409, detail="Own admin account cannot be deactivated")
    active_admins = session.scalar(
        select(func.count()).select_from(User).where(User.role == UserRole.ADMIN.value, User.is_active.is_(True))
    ) or 0
    if user.role == UserRole.ADMIN.value and active_admins <= 1:
        raise HTTPException(status_code=409, detail="Der letzte aktive ADMIN darf nicht deaktiviert werden.")
    user.is_active = False
    session.commit()
    session.refresh(user)
    return user


@router.post("/users/{item_id}/activate", response_model=UserRead)
def activate_user(item_id: str, session: Session = Depends(get_session), _: User = Depends(current_admin)):
    user = session.get(User, item_id)
    if user is None:
        raise HTTPException(status_code=404, detail="Resource not found")
    user.is_active = True
    session.commit()
    session.refresh(user)
    return user


@router.post("/users/{item_id}/reset-password")
def reset_user_password(item_id: str, session: Session = Depends(get_session), _: User = Depends(current_admin)):
    import secrets
    from app.core.security import hash_password

    user = session.get(User, item_id)
    if user is None:
        raise HTTPException(status_code=404, detail="Resource not found")
    temporary_password = secrets.token_urlsafe(12)
    user.password_hash = hash_password(temporary_password)
    user.must_change_password = True
    session.commit()
    return JSONResponse(content={"temporary_password": temporary_password})
