from datetime import UTC, datetime

from fastapi import HTTPException, status
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from starlette.requests import Request

from app.models.repair import Repair
from app.models.user import User
from app.repositories.customer_repository import CustomerRepository
from app.repositories.repair_repository import RepairRepository
from app.schemas.repair import RepairCreate, RepairIntakePayload, RepairStatusUpdate, RepairUpdate
from app.services.audit_service import sanitize, write_audit_log
from app.services.repair_notification_service import RepairNotificationService


def repair_label(repair: Repair) -> str:
    return f"{repair.repair_number} · {repair.customer_name}"


class RepairService:
    @staticmethod
    def create(db: Session, payload: RepairCreate, *, actor: User | None, request: Request | None = None) -> Repair:
        RepairService._validate_customer(db, payload.customer_id)
        repair_number = RepairRepository.get_next_number(db, datetime.now(UTC).year)
        try:
            repair = RepairRepository.create(db, payload, repair_number=repair_number)
        except IntegrityError:
            db.rollback()
            repair_number = RepairRepository.get_next_number(db, datetime.now(UTC).year)
            repair = RepairRepository.create(db, payload, repair_number=repair_number)
        write_audit_log(
            db,
            action="repairs.create",
            entity_type="repairs",
            entity_id=repair.id,
            entity_label=repair_label(repair),
            actor=actor,
            request=request,
            after_data=repair,
        )
        return repair

    @staticmethod
    def update(db: Session, repair: Repair, payload: RepairUpdate, *, actor: User, request: Request) -> Repair:
        RepairService._validate_customer(db, payload.customer_id)
        before_data = sanitize(repair)
        updated = RepairRepository.update(db, repair, payload)
        write_audit_log(
            db,
            action="repairs.update",
            entity_type="repairs",
            entity_id=updated.id,
            entity_label=repair_label(updated),
            actor=actor,
            request=request,
            before_data=before_data,
            after_data=updated,
        )
        return updated

    @staticmethod
    def update_status(db: Session, repair: Repair, payload: RepairStatusUpdate, *, actor: User, request: Request) -> Repair:
        before_data = sanitize(repair)
        updated = RepairRepository.update_status(db, repair, payload, actor_user_id=actor.id)
        write_audit_log(
            db,
            action="repairs.status_update",
            entity_type="repairs",
            entity_id=updated.id,
            entity_label=repair_label(updated),
            actor=actor,
            request=request,
            before_data=before_data,
            after_data=updated,
            metadata={"old_status": before_data.get("status") if isinstance(before_data, dict) else None, "new_status": updated.status},
        )
        try:
            RepairNotificationService.send_status_mail(db, updated, actor=actor, request=request)
        except Exception:
            write_audit_log(
                db,
                action="repairs.status_mail.failed",
                entity_type="repairs",
                entity_id=updated.id,
                entity_label=repair_label(updated),
                actor=actor,
                request=request,
                metadata={"reason": "unexpected_notification_error"},
            )
        return updated

    @staticmethod
    def cancel(db: Session, repair: Repair, *, actor: User, request: Request) -> Repair:
        before_data = sanitize(repair)
        updated = RepairRepository.cancel(db, repair, actor_user_id=actor.id)
        write_audit_log(
            db,
            action="repairs.cancel",
            entity_type="repairs",
            entity_id=updated.id,
            entity_label=repair_label(updated),
            actor=actor,
            request=request,
            before_data=before_data,
            after_data=updated,
        )
        return updated

    @staticmethod
    def create_from_intake(db: Session, payload: RepairIntakePayload, *, request: Request | None = None) -> Repair:
        event = RepairRepository.create_intake_event(
            db,
            external_source="website",
            external_reference=payload.source_reference,
            payload=payload.model_dump(mode="json"),
            status="received",
        )
        try:
            repair_payload = RepairCreate(
                customer_id=None,
                contact_id=None,
                status="new",
                priority="normal",
                source="website",
                source_reference=payload.source_reference,
                customer_name=payload.name,
                customer_email=payload.email,
                customer_phone=payload.phone,
                device_manufacturer=payload.manufacturer,
                device_model=payload.model,
                device_serial_number=payload.serial_number,
                device_type=payload.device_type,
                accessories=payload.accessories,
                fault_description=payload.fault_description,
                previous_work=payload.previous_work,
                device_opened=payload.device_opened,
                intake_notes="Website Intake",
            )
            repair = RepairService.create(db, repair_payload, actor=None, request=request)
            RepairRepository.update_intake_event(db, event, status="processed", repair_id=repair.id)
            write_audit_log(
                db,
                action="repairs.intake_processed",
                entity_type="repairs",
                entity_id=repair.id,
                entity_label=repair_label(repair),
                request=request,
                metadata={"external_source": "website", "external_reference": payload.source_reference},
            )
            return repair
        except Exception as exc:
            RepairRepository.update_intake_event(db, event, status="failed", error_message="Intake konnte nicht verarbeitet werden")
            write_audit_log(
                db,
                action="repairs.intake_failed",
                entity_type="repairs",
                request=request,
                metadata={"external_source": "website", "external_reference": payload.source_reference},
            )
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Reparaturannahme konnte nicht verarbeitet werden",
            ) from exc

    @staticmethod
    def _validate_customer(db: Session, customer_id: int | None) -> None:
        if customer_id is None:
            return
        if CustomerRepository.get_by_id(db, customer_id) is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kunde nicht gefunden")
