"""Complaint workflow.

Statuses move along `COMPLAINT_TRANSITIONS`; every transition is written to the
append-only `complaint_status_history`. `resolved_at`/`closed_at` are plain
timestamps kept for reporting — resolving a ticket (or closing one that was never
resolved) records the moment the problem stopped being open.
"""
from fastapi import HTTPException, status as http_status
from sqlalchemy import func, select
from sqlalchemy.orm import Session

from app.core.time import range_bounds_utc, today_local, utcnow
from app.models.complaint import Complaint, ComplaintStatusHistory
from app.models.enums import COMPLAINT_TRANSITIONS, ComplaintStatus
from app.models.user import User
from app.schemas.complaint import ComplaintStatusChange

#: Tickets still being worked. Everything else has stopped the workflow.
LIVE_STATUSES = (ComplaintStatus.OPEN, ComplaintStatus.IN_PROGRESS)


def _bad_request(detail: str) -> HTTPException:
    return HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, detail=detail)


def allowed_transitions(current: ComplaintStatus) -> list[ComplaintStatus]:
    return sorted(COMPLAINT_TRANSITIONS[current], key=lambda s: s.value)


# --- history ---


def record_history(
    db: Session,
    complaint: Complaint,
    *,
    from_status: ComplaintStatus | None,
    to_status: ComplaintStatus,
    user_id: int | None,
    remarks: str | None = None,
) -> ComplaintStatusHistory:
    entry = ComplaintStatusHistory(
        complaint_id=complaint.id,
        from_status=from_status,
        to_status=to_status,
        changed_by_id=user_id,
        changed_at=utcnow(),
        remarks=remarks,
    )
    db.add(entry)
    return entry


# --- workflow ---


def change_status(
    db: Session,
    complaint: Complaint,
    payload: ComplaintStatusChange,
    actor: User,
) -> None:
    """Apply a status change and audit it."""
    current = complaint.status
    target = payload.status

    if target == current:
        raise _bad_request(f"Complaint is already '{current.value}'")
    if target not in COMPLAINT_TRANSITIONS[current]:
        allowed = ", ".join(s.value for s in allowed_transitions(current)) or "none"
        raise _bad_request(
            f"Cannot move from '{current.value}' to '{target.value}'. Allowed: {allowed}"
        )

    notes = (payload.resolution_notes or "").strip() or None
    # Closing a ticket that was never resolved is also the moment its outcome is
    # recorded, so it needs notes for the same reason `resolved` does.
    needs_notes = target == ComplaintStatus.RESOLVED or (
        target == ComplaintStatus.CLOSED and complaint.resolved_at is None
    )
    if needs_notes and not (notes or complaint.resolution_notes):
        raise _bad_request("Resolution notes are required")

    now = utcnow()

    if notes:
        complaint.resolution_notes = notes

    if target == ComplaintStatus.RESOLVED:
        complaint.resolved_at = now
        complaint.closed_at = None
    elif target == ComplaintStatus.CLOSED:
        complaint.closed_at = now
        if complaint.resolved_at is None:
            complaint.resolved_at = now
    elif target in LIVE_STATUSES:
        # Reopening: the problem is live again, so the resolution timestamp clears.
        complaint.resolved_at = None
        complaint.closed_at = None

    complaint.status = target
    record_history(
        db,
        complaint,
        from_status=current,
        to_status=target,
        user_id=actor.id,
        remarks=payload.remarks,
    )


# --- aggregates ---


def summary(db: Session) -> dict:
    """Headline counts for the module header and the dashboard."""
    active = Complaint.is_deleted.is_(False)
    today_start, today_end = range_bounds_utc(today_local(), today_local())

    def count(*conditions) -> int:
        return db.scalar(select(func.count()).select_from(Complaint).where(active, *conditions)) or 0

    return {
        "total": count(),
        "open": count(Complaint.status == ComplaintStatus.OPEN),
        "in_progress": count(Complaint.status == ComplaintStatus.IN_PROGRESS),
        "unassigned": count(
            Complaint.status.in_(LIVE_STATUSES), Complaint.assigned_technician_id.is_(None)
        ),
        "reported_today": count(
            Complaint.reported_at >= today_start, Complaint.reported_at < today_end
        ),
        "resolved_today": count(
            Complaint.resolved_at >= today_start, Complaint.resolved_at < today_end
        ),
    }
