"""Stunden-Auszahlung: HR/Admin bucht Überstunden aus – oder Mitarbeiter beantragt
Auszahlung und HR genehmigt (Firmen-Opt-in `payout_request_enabled`)."""
from datetime import datetime
from decimal import Decimal
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.database import get_db
from app.core.dependencies import get_client_ip, require_role
from app.models.audit_log import AuditLog
from app.models.company import Company
from app.models.hours_payout import HoursPayout, PayoutStatus
from app.models.overtime_balance import OvertimeBalance
from app.models.user import User, UserRole
from app.schemas.hours_payout import (
    HoursPayoutCreate,
    HoursPayoutListResponse,
    HoursPayoutOut,
    HoursPayoutReject,
    HoursPayoutRequest,
)

router = APIRouter(tags=["Stunden-Auszahlung"])

_hr_roles = (UserRole.HR, UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)
_all_roles = (UserRole.EMPLOYEE, UserRole.MANAGER, UserRole.HR, UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)


async def _build_out(payout: HoursPayout, db: AsyncSession) -> HoursPayoutOut:
    out = HoursPayoutOut.model_validate(payout)
    user = await db.get(User, payout.user_id)
    creator = await db.get(User, payout.created_by)
    out.user_name = f"{user.first_name} {user.last_name}" if user else str(payout.user_id)
    out.created_by_name = f"{creator.first_name} {creator.last_name}" if creator else str(payout.created_by)
    if payout.decided_by:
        decider = await db.get(User, payout.decided_by)
        out.decided_by_name = f"{decider.first_name} {decider.last_name}" if decider else str(payout.decided_by)
    return out


async def _get_or_create_balance(user_id: UUID, company_id: UUID, db: AsyncSession) -> OvertimeBalance:
    ob = await db.scalar(select(OvertimeBalance).where(OvertimeBalance.user_id == user_id))
    if ob is None:
        ob = OvertimeBalance(
            user_id=user_id, company_id=company_id,
            total_hours=Decimal("0"), taken_hours=Decimal("0"),
        )
        db.add(ob)
        await db.flush()
    return ob


# ── GET /hr/payouts ───────────────────────────────────────────────────────────

@router.get("/hr/payouts", response_model=HoursPayoutListResponse)
async def list_payouts(
    user_id: UUID | None = Query(None),
    year: int | None = Query(None, ge=2000, le=2100),
    month: int | None = Query(None, ge=1, le=12),
    status: str | None = Query(None),
    current_user: User = require_role(*_all_roles),
    db: AsyncSession = Depends(get_db),
):
    """Auszahlungen der eigenen Firma, optional gefiltert. EMPLOYEE/MANAGER sehen nur eigene."""
    if current_user.role not in _hr_roles:
        user_id = current_user.id

    filters = [HoursPayout.company_id == current_user.company_id]
    if user_id is not None:
        filters.append(HoursPayout.user_id == user_id)
    if year is not None:
        filters.append(HoursPayout.period_year == year)
    if month is not None:
        filters.append(HoursPayout.period_month == month)
    if status is not None:
        filters.append(HoursPayout.status == status)

    total_count = await db.scalar(select(func.count()).select_from(HoursPayout).where(*filters))
    rows = list(await db.scalars(
        select(HoursPayout).where(*filters).order_by(HoursPayout.created_at.desc())
    ))
    result = [await _build_out(p, db) for p in rows]
    return HoursPayoutListResponse(payouts=result, total_count=total_count or 0)


# ── POST /hr/payouts (HR-Direktbuchung) ───────────────────────────────────────

@router.post("/hr/payouts", response_model=HoursPayoutOut, status_code=201)
async def create_payout(
    request: Request,
    data: HoursPayoutCreate,
    current_user: User = require_role(*_hr_roles),
    db: AsyncSession = Depends(get_db),
):
    """HR bucht Auszahlung direkt (sofort genehmigt) – reduziert sofort den Saldo."""
    target = await db.get(User, data.user_id)
    if not target or target.company_id != current_user.company_id:
        raise HTTPException(404, "Mitarbeiter nicht gefunden")

    hours = Decimal(str(data.hours))
    ob = await _get_or_create_balance(data.user_id, current_user.company_id, db)
    ob.taken_hours += hours  # Überziehung erlaubt (kein Hard-Block, wie FZA)

    payout = HoursPayout(
        company_id=current_user.company_id,
        user_id=data.user_id,
        hours=hours,
        period_year=data.period_year,
        period_month=data.period_month,
        note=data.note,
        status=PayoutStatus.APPROVED.value,
        created_by=current_user.id,
        decided_by=current_user.id,
        decided_at=datetime.utcnow(),
    )
    db.add(payout)
    await db.flush()
    await db.refresh(payout)  # server_default created_at – noch in-Transaktion (RLS-Kontext da)

    db.add(AuditLog(
        company_id=current_user.company_id, user_id=current_user.id,
        action="hours_payout_created", entity_type="hours_payout", entity_id=payout.id,
        new_value={
            "user_id": str(data.user_id), "hours": str(hours),
            "period_year": data.period_year, "period_month": data.period_month, "note": data.note,
        },
        ip=get_client_ip(request),
    ))
    out = await _build_out(payout, db)
    await db.commit()
    return out


# ── POST /hr/payouts/request (Mitarbeiter-Selbstbedienung) ────────────────────

@router.post("/hr/payouts/request", response_model=HoursPayoutOut, status_code=201)
async def request_payout(
    request: Request,
    data: HoursPayoutRequest,
    current_user: User = require_role(*_all_roles),
    db: AsyncSession = Depends(get_db),
):
    """Mitarbeiter beantragt Auszahlung eigener Überstunden. Saldo wird erst bei
    Genehmigung durch HR abgezogen. Nur wenn Firma `payout_request_enabled` hat."""
    company = await db.get(Company, current_user.company_id)
    if not company or not company.payout_request_enabled:
        raise HTTPException(403, "Auszahlungs-Anträge sind für diese Firma nicht aktiviert.")

    hours = Decimal(str(data.hours))
    payout = HoursPayout(
        company_id=current_user.company_id,
        user_id=current_user.id,
        hours=hours,
        period_year=data.period_year,
        period_month=data.period_month,
        note=data.note,
        status=PayoutStatus.REQUESTED.value,
        created_by=current_user.id,
    )
    db.add(payout)
    await db.flush()
    await db.refresh(payout)  # server_default created_at – noch in-Transaktion (RLS-Kontext da)

    db.add(AuditLog(
        company_id=current_user.company_id, user_id=current_user.id,
        action="hours_payout_requested", entity_type="hours_payout", entity_id=payout.id,
        new_value={"hours": str(hours), "note": data.note},
        ip=get_client_ip(request),
    ))
    out = await _build_out(payout, db)
    await db.commit()
    return out


# ── POST /hr/payouts/{id}/approve ─────────────────────────────────────────────

@router.post("/hr/payouts/{payout_id}/approve", response_model=HoursPayoutOut)
async def approve_payout(
    payout_id: UUID,
    request: Request,
    current_user: User = require_role(*_hr_roles),
    db: AsyncSession = Depends(get_db),
):
    """HR genehmigt einen Antrag – erst jetzt wird der Saldo abgezogen."""
    payout = await db.get(HoursPayout, payout_id)
    if payout is None or payout.company_id != current_user.company_id:
        raise HTTPException(404, "Auszahlung nicht gefunden")
    if payout.status != PayoutStatus.REQUESTED.value:
        raise HTTPException(409, "Nur offene Anträge können genehmigt werden.")

    ob = await _get_or_create_balance(payout.user_id, current_user.company_id, db)
    ob.taken_hours += payout.hours

    payout.status = PayoutStatus.APPROVED.value
    payout.decided_by = current_user.id
    payout.decided_at = datetime.utcnow()

    db.add(AuditLog(
        company_id=current_user.company_id, user_id=current_user.id,
        action="hours_payout_approved", entity_type="hours_payout", entity_id=payout.id,
        new_value={"hours": str(payout.hours), "user_id": str(payout.user_id)},
        ip=get_client_ip(request),
    ))
    out = await _build_out(payout, db)
    await db.commit()
    return out


# ── POST /hr/payouts/{id}/reject ──────────────────────────────────────────────

@router.post("/hr/payouts/{payout_id}/reject", response_model=HoursPayoutOut)
async def reject_payout(
    payout_id: UUID,
    request: Request,
    data: HoursPayoutReject,
    current_user: User = require_role(*_hr_roles),
    db: AsyncSession = Depends(get_db),
):
    """HR lehnt Antrag ab – kein Saldo-Abzug."""
    payout = await db.get(HoursPayout, payout_id)
    if payout is None or payout.company_id != current_user.company_id:
        raise HTTPException(404, "Auszahlung nicht gefunden")
    if payout.status != PayoutStatus.REQUESTED.value:
        raise HTTPException(409, "Nur offene Anträge können abgelehnt werden.")

    payout.status = PayoutStatus.REJECTED.value
    payout.rejection_reason = data.rejection_reason
    payout.decided_by = current_user.id
    payout.decided_at = datetime.utcnow()

    db.add(AuditLog(
        company_id=current_user.company_id, user_id=current_user.id,
        action="hours_payout_rejected", entity_type="hours_payout", entity_id=payout.id,
        new_value={"rejection_reason": data.rejection_reason, "user_id": str(payout.user_id)},
        ip=get_client_ip(request),
    ))
    out = await _build_out(payout, db)
    await db.commit()
    return out


# ── POST /hr/payouts/{id}/cancel (Antragsteller zieht zurück) ─────────────────

@router.post("/hr/payouts/{payout_id}/cancel", response_model=HoursPayoutOut)
async def cancel_payout(
    payout_id: UUID,
    request: Request,
    current_user: User = require_role(*_all_roles),
    db: AsyncSession = Depends(get_db),
):
    """Antragsteller zieht seinen offenen Antrag zurück."""
    payout = await db.get(HoursPayout, payout_id)
    if payout is None or payout.company_id != current_user.company_id:
        raise HTTPException(404, "Auszahlung nicht gefunden")
    if payout.user_id != current_user.id:
        raise HTTPException(403, "Nur der eigene Antrag kann zurückgezogen werden.")
    if payout.status != PayoutStatus.REQUESTED.value:
        raise HTTPException(409, "Nur offene Anträge können zurückgezogen werden.")

    payout.status = PayoutStatus.CANCELLED.value
    db.add(AuditLog(
        company_id=current_user.company_id, user_id=current_user.id,
        action="hours_payout_cancelled", entity_type="hours_payout", entity_id=payout.id,
        ip=get_client_ip(request),
    ))
    out = await _build_out(payout, db)
    await db.commit()
    return out


# ── DELETE /hr/payouts/{id} ───────────────────────────────────────────────────

@router.delete("/hr/payouts/{payout_id}", status_code=204)
async def delete_payout(
    payout_id: UUID,
    request: Request,
    current_user: User = require_role(*_hr_roles),
    db: AsyncSession = Depends(get_db),
):
    """Auszahlung stornieren – bei genehmigten Buchungen werden die Stunden zurückgebucht."""
    payout = await db.get(HoursPayout, payout_id)
    if payout is None or payout.company_id != current_user.company_id:
        raise HTTPException(404, "Auszahlung nicht gefunden")

    # Nur genehmigte/gebuchte Auszahlungen haben den Saldo belastet → zurückbuchen
    if payout.status == PayoutStatus.APPROVED.value:
        ob = await db.scalar(select(OvertimeBalance).where(OvertimeBalance.user_id == payout.user_id))
        if ob is not None:
            ob.taken_hours = max(Decimal("0"), ob.taken_hours - payout.hours)

    db.add(AuditLog(
        company_id=current_user.company_id, user_id=current_user.id,
        action="hours_payout_deleted", entity_type="hours_payout", entity_id=payout.id,
        old_value={
            "user_id": str(payout.user_id), "hours": str(payout.hours),
            "status": payout.status,
            "period_year": payout.period_year, "period_month": payout.period_month, "note": payout.note,
        },
        ip=get_client_ip(request),
    ))
    await db.delete(payout)
    await db.commit()
