from __future__ import annotations

from datetime import datetime, timedelta, timezone
from decimal import Decimal
from uuid import UUID

from fastapi import APIRouter, Depends
from sqlalchemy import func
from sqlalchemy.orm import Session

from app.core.deps import require_role
from app.db.session import get_db
from app.models.admin_user import AdminUser
from app.models.audit_log import AuditLog
from app.models.request import Request
from app.models.request_service_request import RequestServiceRequest
from app.models.status import Status
from app.models.status_history import StatusHistory
from app.services.notifications import (
    unread_admin_summary,
    unread_global_summary_for_clients,
    unread_global_summary_for_lawyers,
)
from app.services.sla_metrics import compute_sla_snapshot

router = APIRouter()

DEFAULT_TERMINAL_STATUS_CODES = {"RESOLVED", "CLOSED", "REJECTED"}
PAID_STATUS_CODES = {"PAID", "ОПЛАЧЕНО"}


def _terminal_status_codes(db: Session) -> set[str]:
    rows = db.query(Status.code).filter(Status.is_terminal.is_(True)).all()
    codes = {str(code).strip() for (code,) in rows if code}
    return codes or set(DEFAULT_TERMINAL_STATUS_CODES)


def _paid_status_codes() -> set[str]:
    return set(PAID_STATUS_CODES)


def _month_bounds(now_utc: datetime) -> tuple[datetime, datetime]:
    start = datetime(now_utc.year, now_utc.month, 1, tzinfo=timezone.utc)
    if now_utc.month == 12:
        end = datetime(now_utc.year + 1, 1, 1, tzinfo=timezone.utc)
    else:
        end = datetime(now_utc.year, now_utc.month + 1, 1, tzinfo=timezone.utc)
    return start, end


def _to_float(value) -> float:
    if value is None:
        return 0.0
    if isinstance(value, Decimal):
        return float(value)
    try:
        return float(value)
    except (TypeError, ValueError):
        return 0.0


def _uuid_or_none(value: str | None) -> UUID | None:
    try:
        return UUID(str(value or ""))
    except ValueError:
        return None


def _extract_assigned_lawyer_from_audit(diff: dict | None, action: str | None) -> str | None:
    if not isinstance(diff, dict):
        return None
    action_code = str(action or "").upper()
    if action_code == "MANUAL_CLAIM":
        value = diff.get("assigned_lawyer_id")
        return str(value).strip() if value else None
    if action_code == "MANUAL_REASSIGN":
        value = diff.get("to_lawyer_id")
        return str(value).strip() if value else None
    if action_code in {"CREATE", "UPDATE"}:
        after = diff.get("after")
        before = diff.get("before")
        if action_code == "UPDATE":
            if not isinstance(after, dict) or not isinstance(before, dict):
                return None
            prev_value = str(before.get("assigned_lawyer_id") or "").strip()
            next_value = str(after.get("assigned_lawyer_id") or "").strip()
            if not next_value or next_value == prev_value:
                return None
            return next_value
        if isinstance(after, dict):
            value = str(after.get("assigned_lawyer_id") or "").strip()
            return value or None
    return None


def _empty_sla_snapshot() -> dict[str, object]:
    return {
        "frt_avg_minutes": None,
        "sla_overdue": 0,
        "overdue_by_status": {},
        "overdue_by_transition": {},
        "avg_time_in_status_hours": {},
    }


def _overview_sla_payload(db: Session) -> dict[str, object]:
    sla_snapshot = compute_sla_snapshot(db)
    return {
        "frt_avg_minutes": sla_snapshot.get("frt_avg_minutes"),
        "sla_overdue": sla_snapshot.get("overdue_total", 0),
        "overdue_by_status": sla_snapshot.get("overdue_by_status", {}),
        "overdue_by_transition": sla_snapshot.get("overdue_by_transition", {}),
        "avg_time_in_status_hours": sla_snapshot.get("avg_time_in_status_hours", {}),
    }


@router.get("/overview")
def overview(
    include_sla: bool = True,
    db: Session = Depends(get_db),
    admin=Depends(require_role("ADMIN", "LAWYER", "CURATOR")),
):
    role = str(admin.get("role") or "").upper()
    actor_id = str(admin.get("sub") or "").strip()
    actor_uuid = _uuid_or_none(actor_id)

    terminal_codes = _terminal_status_codes(db)
    paid_codes = _paid_status_codes()
    now_utc = datetime.now(timezone.utc)
    month_start, next_month_start = _month_bounds(now_utc)

    unread_for_clients_flags = (
        db.query(func.count(Request.id))
        .filter(Request.client_has_unread_updates.is_(True))
        .scalar()
        or 0
    )
    unread_for_lawyers_flags = (
        db.query(func.count(Request.id))
        .filter(Request.lawyer_has_unread_updates.is_(True))
        .scalar()
        or 0
    )
    unread_for_clients_notifications = unread_global_summary_for_clients(db)
    unread_for_lawyers_notifications = unread_global_summary_for_lawyers(db)
    unread_for_clients = max(int(unread_for_clients_flags), int(unread_for_clients_notifications.get("total") or 0))
    unread_for_lawyers = max(int(unread_for_lawyers_flags), int(unread_for_lawyers_notifications.get("total") or 0))
    my_unread_notifications = (
        unread_admin_summary(db, admin_user_id=str(actor_uuid), request_id=None) if actor_uuid is not None else {"total": 0, "by_event": {}}
    )
    if role == "LAWYER" and actor_uuid is not None:
        service_request_unread_total = int(
            db.query(func.count(RequestServiceRequest.id))
            .filter(
                RequestServiceRequest.type == "CURATOR_CONTACT",
                RequestServiceRequest.assigned_lawyer_id == str(actor_uuid),
                RequestServiceRequest.lawyer_unread.is_(True),
            )
            .scalar()
            or 0
        )
    elif role == "LAWYER":
        service_request_unread_total = 0
    else:
        service_request_unread_total = int(
            db.query(func.count(RequestServiceRequest.id))
            .filter(RequestServiceRequest.admin_unread.is_(True))
            .scalar()
            or 0
        )

    active_load_rows = (
        db.query(Request.assigned_lawyer_id, func.count(Request.id))
        .filter(Request.assigned_lawyer_id.is_not(None))
        .filter(Request.status_code.notin_(terminal_codes))
        .group_by(Request.assigned_lawyer_id)
        .all()
    )
    total_load_rows = (
        db.query(Request.assigned_lawyer_id, func.count(Request.id))
        .filter(Request.assigned_lawyer_id.is_not(None))
        .group_by(Request.assigned_lawyer_id)
        .all()
    )
    active_amount_rows = (
        db.query(Request.assigned_lawyer_id, func.coalesce(func.sum(func.coalesce(Request.invoice_amount, 0)), 0))
        .filter(Request.assigned_lawyer_id.is_not(None))
        .filter(Request.status_code.notin_(terminal_codes))
        .group_by(Request.assigned_lawyer_id)
        .all()
    )
    paid_rows = (
        db.query(
            Request.assigned_lawyer_id,
            func.count(StatusHistory.id),
            func.coalesce(func.sum(func.coalesce(Request.invoice_amount, 0)), 0),
        )
        .join(StatusHistory, StatusHistory.request_id == Request.id)
        .filter(Request.assigned_lawyer_id.is_not(None))
        .filter(StatusHistory.created_at >= month_start, StatusHistory.created_at < next_month_start)
        .filter(func.upper(StatusHistory.to_status).in_(paid_codes))
        .group_by(Request.assigned_lawyer_id)
        .all()
    )

    active_load_map = {str(lawyer_id): int(count) for lawyer_id, count in active_load_rows if lawyer_id}
    total_load_map = {str(lawyer_id): int(count) for lawyer_id, count in total_load_rows if lawyer_id}
    active_amount_map = {str(lawyer_id): _to_float(amount) for lawyer_id, amount in active_amount_rows if lawyer_id}
    paid_events_map = {str(lawyer_id): int(events) for lawyer_id, events, _ in paid_rows if lawyer_id}
    monthly_gross_map = {str(lawyer_id): _to_float(gross) for lawyer_id, _, gross in paid_rows if lawyer_id}

    monthly_completed_rows = (
        db.query(Request.assigned_lawyer_id, func.count(func.distinct(StatusHistory.request_id)))
        .join(StatusHistory, StatusHistory.request_id == Request.id)
        .filter(Request.assigned_lawyer_id.is_not(None))
        .filter(StatusHistory.created_at >= month_start, StatusHistory.created_at < next_month_start)
        .filter(func.upper(StatusHistory.to_status).in_({str(code).upper() for code in terminal_codes}))
        .group_by(Request.assigned_lawyer_id)
        .all()
    )
    monthly_completed_map = {str(lawyer_id): int(count) for lawyer_id, count in monthly_completed_rows if lawyer_id}

    monthly_assigned_map: dict[str, int] = {}
    audit_rows = (
        db.query(AuditLog.action, AuditLog.diff)
        .filter(AuditLog.entity == "requests")
        .filter(AuditLog.created_at >= month_start, AuditLog.created_at < next_month_start)
        .all()
    )
    for action, diff in audit_rows:
        assigned_to = _extract_assigned_lawyer_from_audit(diff, action)
        if not assigned_to:
            continue
        monthly_assigned_map[assigned_to] = int(monthly_assigned_map.get(assigned_to, 0)) + 1

    monthly_revenue = round(sum(monthly_gross_map.values()), 2)

    lawyers = (
        db.query(AdminUser)
        .filter(AdminUser.role == "LAWYER", AdminUser.is_active.is_(True))
        .all()
    )
    lawyer_loads = []
    for lawyer in lawyers:
        lawyer_id = str(lawyer.id)
        salary_percent = _to_float(lawyer.salary_percent)
        monthly_paid_gross = monthly_gross_map.get(lawyer_id, 0.0)
        monthly_salary = monthly_paid_gross * salary_percent / 100.0
        lawyer_loads.append(
            {
                "lawyer_id": lawyer_id,
                "name": lawyer.name,
                "email": lawyer.email,
                "avatar_url": lawyer.avatar_url,
                "primary_topic_code": lawyer.primary_topic_code,
                "default_rate": _to_float(lawyer.default_rate),
                "salary_percent": salary_percent,
                "active_load": active_load_map.get(lawyer_id, 0),
                "total_assigned": total_load_map.get(lawyer_id, 0),
                "active_amount": round(active_amount_map.get(lawyer_id, 0.0), 2),
                "monthly_assigned_count": monthly_assigned_map.get(lawyer_id, 0),
                "monthly_completed_count": monthly_completed_map.get(lawyer_id, 0),
                "monthly_paid_events": paid_events_map.get(lawyer_id, 0),
                "monthly_paid_gross": round(monthly_paid_gross, 2),
                "monthly_salary": round(monthly_salary, 2),
            }
        )
    lawyer_loads.sort(key=lambda row: (-row["active_load"], row["name"] or "", row["email"] or ""))

    if role == "LAWYER" and actor_uuid is not None:
        scoped_by_status_rows = (
            db.query(Request.status_code, func.count(Request.id))
            .filter(Request.assigned_lawyer_id == str(actor_uuid))
            .group_by(Request.status_code)
            .all()
        )
        by_status = {status: int(count) for status, count in scoped_by_status_rows}
        assigned_total = int(sum(by_status.values()))
        active_assigned_total = int(
            db.query(func.count(Request.id))
            .filter(Request.assigned_lawyer_id == str(actor_uuid))
            .filter(Request.status_code.notin_(terminal_codes))
            .scalar()
            or 0
        )
        unassigned_total = int(db.query(func.count(Request.id)).filter(Request.assigned_lawyer_id.is_(None)).scalar() or 0)
        my_unread_updates = int(
            db.query(func.count(Request.id))
            .filter(
                Request.assigned_lawyer_id == str(actor_uuid),
                Request.lawyer_has_unread_updates.is_(True),
            )
            .scalar()
            or 0
        )
        my_unread_by_event_rows = (
            db.query(Request.lawyer_unread_event_type, func.count(Request.id))
            .filter(
                Request.assigned_lawyer_id == str(actor_uuid),
                Request.lawyer_has_unread_updates.is_(True),
                Request.lawyer_unread_event_type.is_not(None),
            )
            .group_by(Request.lawyer_unread_event_type)
            .all()
        )
        my_unread_by_event = {str(event_type): int(count) for event_type, count in my_unread_by_event_rows if event_type}
        notif_total = int(my_unread_notifications.get("total") or 0)
        notif_by_event = dict(my_unread_notifications.get("by_event") or {})
        if notif_total > my_unread_updates:
            my_unread_updates = notif_total
            my_unread_by_event = notif_by_event
        scoped_lawyer_loads = [row for row in lawyer_loads if str(row["lawyer_id"]) == str(actor_uuid)]
    elif role == "LAWYER":
        by_status = {}
        assigned_total = 0
        active_assigned_total = 0
        unassigned_total = int(db.query(func.count(Request.id)).filter(Request.assigned_lawyer_id.is_(None)).scalar() or 0)
        my_unread_updates = 0
        my_unread_by_event = {}
        scoped_lawyer_loads = []
    else:
        scoped_by_status_rows = db.query(Request.status_code, func.count(Request.id)).group_by(Request.status_code).all()
        by_status = {status: int(count) for status, count in scoped_by_status_rows}
        assigned_total = int(
            db.query(func.count(Request.id))
            .filter(Request.assigned_lawyer_id.is_not(None))
            .scalar()
            or 0
        )
        active_assigned_total = int(
            db.query(func.count(Request.id))
            .filter(Request.assigned_lawyer_id.is_not(None))
            .filter(Request.status_code.notin_(terminal_codes))
            .scalar()
            or 0
        )
        unassigned_total = int(db.query(func.count(Request.id)).filter(Request.assigned_lawyer_id.is_(None)).scalar() or 0)
        my_unread_updates = int(my_unread_notifications.get("total") or 0)
        my_unread_by_event = dict(my_unread_notifications.get("by_event") or {})
        scoped_lawyer_loads = lawyer_loads

    next_day_start = datetime(now_utc.year, now_utc.month, now_utc.day, tzinfo=timezone.utc) + timedelta(days=1)
    deadline_alert_query = (
        db.query(func.count(Request.id))
        .filter(Request.important_date_at.is_not(None))
        .filter(Request.important_date_at < next_day_start)
        .filter(Request.status_code.notin_(terminal_codes))
    )
    if role == "LAWYER" and actor_uuid is not None:
        deadline_alert_query = deadline_alert_query.filter(Request.assigned_lawyer_id == str(actor_uuid))
    elif role == "LAWYER":
        deadline_alert_query = deadline_alert_query.filter(Request.id.is_(None))
    deadline_alert_total = int(deadline_alert_query.scalar() or 0)
    payload = {
        "scope": role if role in {"ADMIN", "LAWYER", "CURATOR"} else "ADMIN",
        "new": int(by_status.get("NEW", 0)),
        "by_status": by_status,
        "assigned_total": assigned_total,
        "active_assigned_total": active_assigned_total,
        "unassigned_total": unassigned_total,
        "my_unread_updates": my_unread_updates,
        "my_unread_by_event": my_unread_by_event,
        "my_unread_notifications_total": int(my_unread_notifications.get("total") or 0),
        "my_unread_notifications_by_event": dict(my_unread_notifications.get("by_event") or {}),
        "deadline_alert_total": deadline_alert_total,
        "month_revenue": monthly_revenue,
        "month_expenses": round(sum(_to_float(row.get("monthly_salary")) for row in scoped_lawyer_loads), 2)
        if role == "LAWYER"
        else round(sum(_to_float(row.get("monthly_salary")) for row in lawyer_loads), 2),
        "unread_for_clients": int(unread_for_clients),
        "unread_for_lawyers": int(unread_for_lawyers),
        "unread_for_clients_by_event": dict(unread_for_clients_notifications.get("by_event") or {}),
        "unread_for_lawyers_by_event": dict(unread_for_lawyers_notifications.get("by_event") or {}),
        "unread_for_clients_notifications_total": int(unread_for_clients_notifications.get("total") or 0),
        "unread_for_lawyers_notifications_total": int(unread_for_lawyers_notifications.get("total") or 0),
        "service_request_unread_total": int(service_request_unread_total),
        "lawyer_loads": scoped_lawyer_loads,
    }
    payload.update(_overview_sla_payload(db) if include_sla else _empty_sla_snapshot())
    return payload


@router.get("/overview-sla")
def overview_sla(db: Session = Depends(get_db), admin=Depends(require_role("ADMIN", "LAWYER", "CURATOR"))):
    _ = admin
    return _overview_sla_payload(db)


@router.get("/lawyers/{lawyer_id}/active-requests")
def lawyer_active_requests(
    lawyer_id: str,
    db: Session = Depends(get_db),
    admin=Depends(require_role("ADMIN", "LAWYER")),
):
    actor_role = str(admin.get("role") or "").upper()
    actor_id = str(admin.get("sub") or "").strip()
    if actor_role == "LAWYER" and str(lawyer_id) != actor_id:
        return {"rows": [], "total": 0, "totals": {"amount": 0.0, "salary": 0.0}}

    lawyer = db.query(AdminUser).filter(AdminUser.id == _uuid_or_none(lawyer_id), AdminUser.role == "LAWYER").first()
    if lawyer is None:
        return {"rows": [], "total": 0, "totals": {"amount": 0.0, "salary": 0.0}}

    terminal_codes = _terminal_status_codes(db)
    paid_codes = _paid_status_codes()
    now_utc = datetime.now(timezone.utc)
    month_start, next_month_start = _month_bounds(now_utc)

    salary_percent = _to_float(lawyer.salary_percent)
    paid_by_request_rows = (
        db.query(StatusHistory.request_id, func.count(StatusHistory.id))
        .join(Request, Request.id == StatusHistory.request_id)
        .filter(Request.assigned_lawyer_id == str(lawyer.id))
        .filter(StatusHistory.created_at >= month_start, StatusHistory.created_at < next_month_start)
        .filter(func.upper(StatusHistory.to_status).in_(paid_codes))
        .group_by(StatusHistory.request_id)
        .all()
    )
    paid_events_per_request = {str(req_id): int(count) for req_id, count in paid_by_request_rows if req_id}

    request_rows = (
        db.query(Request)
        .filter(Request.assigned_lawyer_id == str(lawyer.id))
        .filter(Request.status_code.notin_(terminal_codes))
        .order_by(Request.created_at.desc(), Request.track_number.asc())
        .all()
    )

    rows = []
    total_amount = 0.0
    total_salary = 0.0
    for req in request_rows:
        req_id = str(req.id)
        invoice_amount = _to_float(req.invoice_amount)
        paid_events = int(paid_events_per_request.get(req_id, 0))
        month_paid_amount = round(invoice_amount * paid_events, 2)
        month_salary_amount = round(month_paid_amount * salary_percent / 100.0, 2)
        total_amount += month_paid_amount
        total_salary += month_salary_amount
        rows.append(
            {
                "id": req_id,
                "track_number": req.track_number,
                "status_code": req.status_code,
                "client_name": req.client_name,
                "invoice_amount": round(invoice_amount, 2),
                "month_paid_events": paid_events,
                "month_paid_amount": month_paid_amount,
                "month_salary_amount": month_salary_amount,
                "created_at": req.created_at.isoformat() if req.created_at else None,
                "paid_at": req.paid_at.isoformat() if req.paid_at else None,
            }
        )

    return {
        "rows": rows,
        "total": len(rows),
        "totals": {
            "amount": round(total_amount, 2),
            "salary": round(total_salary, 2),
        },
    }
