from uuid import UUID

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

from app.core.database import get_db
from app.core.dependencies import CurrentUser, get_client_ip, require_role
from app.models.absence import AbsenceStatus
from app.models.user import User, UserRole
from app.models.overtime_balance import OvertimeBalance
from app.schemas.absence import (
    AbsenceCreate,
    AbsenceListResponse,
    AbsenceOut,
    AbsenceCommentCreate,
    AbsenceCommentOut,
    AbsenceReject,
    AbsenceUpdate,
    AbsenceTypeCreate,
    AbsenceTypeOut,
    AbsenceTypeUpdate,
    CalendarEntry,
    CancellationRequest,
    CertificateMarkIn,
    OvertimeBalanceOut,
    PublicHolidayCreate,
    PublicHolidayOut,
    QuickSickIn,
    SickStatsOut,
    VacationBalanceOut,
    VacationBalanceUpdate,
)
from app.services.absence_service import absence_service
from app.models.company import Company
from sqlalchemy import select
from datetime import date

router = APIRouter(tags=["Abwesenheiten"])


def _carryover_expiry(company: Company, year: int) -> tuple[date | None, bool]:
    """Verfallsdatum für Resturlaub. Delegiert an den Service (eine Quelle der Wahrheit)."""
    return absence_service._carryover_expired(company, year)

_admin_roles = (UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)
_manager_roles = (UserRole.MANAGER, UserRole.HR, UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)


# ── Absence Types ─────────────────────────────────────────────────────────────

@router.get("/absence-types/", response_model=list[AbsenceTypeOut])
async def list_absence_types(
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    types = await absence_service.list_types(current_user.company_id, db)
    return [AbsenceTypeOut.model_validate(t) for t in types]


@router.post("/absence-types/", response_model=AbsenceTypeOut, status_code=201)
async def create_absence_type(
    data: AbsenceTypeCreate,
    current_user: User = require_role(*_admin_roles),
    db: AsyncSession = Depends(get_db),
):
    at = await absence_service.create_type(current_user.company_id, data, db)
    await db.commit()
    return AbsenceTypeOut.model_validate(at)


@router.patch("/absence-types/{type_id}", response_model=AbsenceTypeOut)
async def update_absence_type(
    type_id: UUID,
    data: AbsenceTypeUpdate,
    current_user: User = require_role(*_admin_roles),
    db: AsyncSession = Depends(get_db),
):
    at = await absence_service.update_type(type_id, current_user.company_id, data, db)
    await db.commit()
    return AbsenceTypeOut.model_validate(at)


# ── Public Holidays ───────────────────────────────────────────────────────────

@router.get("/public-holidays/", response_model=list[PublicHolidayOut])
async def list_public_holidays(
    current_user: CurrentUser,
    year: int = Query(...),
    country: str = Query("DE"),
    state: str | None = Query(None),
    db: AsyncSession = Depends(get_db),
):
    holidays = await absence_service.list_holidays(year, country, state, db)
    return [PublicHolidayOut.model_validate(h) for h in holidays]


@router.post("/public-holidays/", response_model=PublicHolidayOut, status_code=201)
async def create_public_holiday(
    data: PublicHolidayCreate,
    current_user: User = require_role(*_admin_roles),
    db: AsyncSession = Depends(get_db),
):
    holiday = await absence_service.create_holiday(data, db)
    await db.commit()
    return PublicHolidayOut.model_validate(holiday)


# ── Absences ──────────────────────────────────────────────────────────────────

@router.get("/absences/calendar", response_model=list[CalendarEntry])
async def get_calendar(
    current_user: CurrentUser,
    year: int = Query(...),
    month: int | None = Query(None, ge=1, le=12),
    db: AsyncSession = Depends(get_db),
):
    """Team-Kalender: alle Abwesenheiten im Zeitraum."""
    entries = await absence_service.get_calendar(current_user.company_id, year, month, db)
    return [CalendarEntry(**e) for e in entries]


@router.get("/absences/balance", response_model=VacationBalanceOut)
async def get_own_balance(
    current_user: CurrentUser,
    year: int = Query(...),
    db: AsyncSession = Depends(get_db),
):
    """Eigenes Urlaubskonto."""
    balance = await absence_service.get_balance(current_user.id, year, db)
    pending = await absence_service.get_pending_days(current_user.id, year, db)
    company = await db.get(Company, current_user.company_id)
    expires_at, expired = _carryover_expiry(company, year) if company else (None, False)
    return VacationBalanceOut.model_validate(balance).model_copy(update={
        "pending_days": pending,
        "available_days": absence_service.effective_available(balance, expired),
        "carried_over_expires_at": expires_at,
        "carried_over_expired": expired,
    })


@router.get("/absences/balance/{user_id}", response_model=VacationBalanceOut)
async def get_balance_for_user(
    user_id: UUID,
    year: int = Query(...),
    current_user: User = require_role(*_manager_roles),
    db: AsyncSession = Depends(get_db),
):
    """Urlaubskonto eines Mitarbeiters (MANAGER/HR/ADMIN)."""
    target_user = await db.get(User, user_id)
    if target_user is None or target_user.company_id != current_user.company_id:
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail="Mitarbeiter nicht gefunden.")
    balance = await absence_service.get_balance(user_id, year, db)
    pending = await absence_service.get_pending_days(user_id, year, db)
    company = await db.get(Company, current_user.company_id)
    expires_at, expired = _carryover_expiry(company, year) if company else (None, False)
    return VacationBalanceOut.model_validate(balance).model_copy(update={
        "pending_days": pending,
        "available_days": absence_service.effective_available(balance, expired),
        "carried_over_expires_at": expires_at,
        "carried_over_expired": expired,
    })


@router.post("/absences/quick-sick", response_model=AbsenceOut, status_code=201)
async def quick_sick(
    data: QuickSickIn,
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    """Sofort-Krankmeldung (auto-approved). Nutzt den ersten aktiven SICK-Typ der Firma."""
    absence, _ = await absence_service.quick_sick(
        data.start_date, data.end_date, current_user, db
    )
    await db.commit()
    return AbsenceOut.model_validate(absence)


@router.get("/absences/sick-stats", response_model=list[SickStatsOut])
async def get_sick_stats(
    current_user: User = require_role(*_manager_roles),
    user_id: UUID | None = Query(None),
    ref_date: date | None = Query(None, description="Stichtag, default heute"),
    db: AsyncSession = Depends(get_db),
):
    """Krankheitsstatistik (rolling 12 Monate ab ref_date) inkl. Bradford-Faktor."""
    stats = await absence_service.get_sick_stats(
        company_id=current_user.company_id,
        current_user=current_user,
        ref_date=ref_date or date.today(),
        db=db,
        user_id=user_id,
    )
    return [SickStatsOut(**s) for s in stats]


@router.get("/absences/overtime-balance", response_model=OvertimeBalanceOut)
async def get_overtime_balance(
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    """Eigenes Überstunden-Konto."""
    bal = await db.scalar(
        select(OvertimeBalance).where(OvertimeBalance.user_id == current_user.id)
    )
    if bal is None:
        return OvertimeBalanceOut(total_hours=0, taken_hours=0, available_hours=0)

    # Verfall anwenden wenn nötig
    from app.services.report_service import apply_overtime_expiry_if_needed
    from app.models.company import Company as CompanyModel
    company = await db.get(CompanyModel, current_user.company_id)
    changed = await apply_overtime_expiry_if_needed(bal, company, db)
    if changed:
        await db.commit()

    return OvertimeBalanceOut(
        total_hours=float(bal.total_hours),
        taken_hours=float(bal.taken_hours),
        available_hours=float(bal.available_hours),
    )


@router.post("/absences/overtime-balance/apply-expiry")
async def apply_overtime_expiry_all(
    current_user: User = require_role(UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN),
    db: AsyncSession = Depends(get_db),
):
    """Überstunden-Verfall manuell für alle Mitarbeiter der Firma anwenden."""
    from sqlalchemy import select as sa_select
    from app.models.company import Company as CompanyModel
    from app.services.report_service import apply_overtime_expiry_if_needed

    company = await db.get(CompanyModel, current_user.company_id)
    balances = list(await db.scalars(
        sa_select(OvertimeBalance).where(OvertimeBalance.company_id == current_user.company_id)
    ))
    applied_count = 0
    for bal in balances:
        changed = await apply_overtime_expiry_if_needed(bal, company, db)
        if changed:
            applied_count += 1
    await db.commit()
    return {"applied_to": applied_count, "total": len(balances)}


@router.get("/absences/", response_model=AbsenceListResponse)
async def list_absences(
    current_user: CurrentUser,
    user_id: UUID | None = Query(None),
    type_id: UUID | None = Query(None),
    status: AbsenceStatus | None = Query(None),
    year: int | None = Query(None),
    as_substitute: bool = Query(False),
    db: AsyncSession = Depends(get_db),
):
    total, absences = await absence_service.list_absences(
        current_user.company_id, current_user, db,
        user_id=user_id, type_id=type_id, status=status, year=year,
        as_substitute=as_substitute,
    )
    return AbsenceListResponse(total=total, items=[AbsenceOut.model_validate(a) for a in absences])


@router.post("/absences/", response_model=AbsenceOut, status_code=201)
async def create_absence(
    data: AbsenceCreate,
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    """Abwesenheitsantrag stellen. HR/Admin kann for_user_id setzen um für andere anzulegen."""
    acting_user = current_user
    if data.for_user_id and data.for_user_id != current_user.id:
        if current_user.role not in _manager_roles:
            from fastapi import HTTPException
            raise HTTPException(status_code=403, detail="Keine Berechtigung, Abwesenheiten für andere anzulegen.")
        target = await db.get(User, data.for_user_id)
        if not target or target.company_id != current_user.company_id:
            from fastapi import HTTPException
            raise HTTPException(status_code=404, detail="Mitarbeiter nicht gefunden.")
        acting_user = target
    absence, warnings = await absence_service.create_absence(data, acting_user, db)
    await db.commit()
    return AbsenceOut.model_validate(absence)


@router.patch("/absences/{absence_id}", response_model=AbsenceOut)
async def update_absence(
    absence_id: UUID,
    data: AbsenceUpdate,
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    """Ausstehenden Antrag bearbeiten (Mitarbeiter: eigene; Manager: alle der Company)."""
    absence = await absence_service.update_absence(absence_id, data, current_user, db)
    await db.commit()
    return AbsenceOut.model_validate(absence)


@router.get("/absences/{absence_id}", response_model=AbsenceOut)
async def get_absence(
    absence_id: UUID,
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    absence = await absence_service.get_by_id(absence_id, current_user, db)
    return AbsenceOut.model_validate(absence)


@router.delete("/absences/{absence_id}", response_model=AbsenceOut)
async def cancel_absence(
    absence_id: UUID,
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    """Antrag stornieren.
    Eigene PENDING-Anträge: alle Rollen.
    APPROVED-Anträge: nur HR/COMPANY_ADMIN/SUPER_ADMIN (mit Rückbuchung von FZA-Stunden).
    """
    absence = await absence_service.cancel_absence(absence_id, current_user, db)
    await db.commit()
    return AbsenceOut.model_validate(absence)


@router.post("/absences/{absence_id}/request-cancellation", response_model=AbsenceOut)
async def request_cancellation(
    absence_id: UUID,
    data: CancellationRequest,
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    """Mitarbeiter beantragt Stornierung eines genehmigten Antrags (Manager genehmigt/lehnt ab).

    HR/Admin storniert weiterhin direkt über DELETE."""
    absence = await absence_service.request_cancellation(absence_id, data.reason, current_user, db)
    await db.commit()
    return AbsenceOut.model_validate(absence)


# ── Kommentare ────────────────────────────────────────────────────────────────

@router.get("/absences/{absence_id}/comments", response_model=list[AbsenceCommentOut])
async def list_absence_comments(
    absence_id: UUID,
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    return await absence_service.list_comments(absence_id, current_user, db)


@router.post("/absences/{absence_id}/comments", response_model=AbsenceCommentOut, status_code=201)
async def add_absence_comment(
    absence_id: UUID,
    data: AbsenceCommentCreate,
    current_user: CurrentUser,
    db: AsyncSession = Depends(get_db),
):
    comment = await absence_service.add_comment(absence_id, data.body, current_user, db)
    await db.commit()
    return comment


class AbsenceApproveOut(AbsenceOut):
    warnings: list[str] = []


@router.post("/absences/{absence_id}/approve", response_model=AbsenceApproveOut)
async def approve_absence(
    absence_id: UUID,
    current_user: User = require_role(*_manager_roles),
    db: AsyncSession = Depends(get_db),
):
    absence, warnings = await absence_service.approve_absence(absence_id, current_user, db)
    await db.commit()
    out = AbsenceApproveOut.model_validate(absence)
    out.warnings = warnings
    return out


@router.post("/absences/{absence_id}/reject", response_model=AbsenceOut)
async def reject_absence(
    absence_id: UUID,
    data: AbsenceReject,
    current_user: User = require_role(*_manager_roles),
    db: AsyncSession = Depends(get_db),
):
    absence = await absence_service.reject_absence(absence_id, data, current_user, db)
    await db.commit()
    return AbsenceOut.model_validate(absence)


@router.patch("/absences/{absence_id}/certificate", response_model=AbsenceOut)
async def mark_certificate_received(
    absence_id: UUID,
    data: CertificateMarkIn,
    current_user: User = require_role(UserRole.HR, UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN),
    db: AsyncSession = Depends(get_db),
):
    """HR/Admin markiert die AU-Bescheinigung als eingegangen."""
    absence = await absence_service.mark_certificate_received(
        absence_id, data.received_at, current_user, db
    )
    await db.commit()
    return AbsenceOut.model_validate(absence)


# ── Urlaubskonto bearbeiten (HR/Admin) ────────────────────────────────────────

@router.patch("/absences/balance/{user_id}", response_model=VacationBalanceOut)
async def update_balance(
    user_id: UUID,
    data: VacationBalanceUpdate,
    request: Request,
    current_user: User = require_role(UserRole.HR, UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN),
    db: AsyncSession = Depends(get_db),
    year: int = Query(...),
):
    """Grundurlaub, Sondertage und Resturlaub eines Mitarbeiters anpassen."""
    from app.models.vacation_balance import VacationBalance
    from app.models.audit_log import AuditLog
    target = await db.get(User, user_id)
    if target is None or target.company_id != current_user.company_id:
        from fastapi import HTTPException
        raise HTTPException(404, "Mitarbeiter nicht gefunden")

    balance = await absence_service.get_balance(user_id, year, db)

    # Alte Werte für AuditLog sichern
    old_value = {
        "entitled_days": balance.entitled_days,
        "special_days": balance.special_days,
        "carried_over": balance.carried_over,
    }

    for field, value in data.model_dump(exclude_unset=True).items():
        setattr(balance, field, value)

    # AuditLog schreiben
    db.add(AuditLog(
        company_id=current_user.company_id,
        user_id=current_user.id,
        action="update_vacation_balance",
        entity_type="vacation_balance",
        entity_id=balance.id,
        old_value=old_value,
        new_value={
            "entitled_days": balance.entitled_days,
            "special_days": balance.special_days,
            "carried_over": balance.carried_over,
            "target_user_id": str(user_id),
            "year": year,
        },
        ip=get_client_ip(request),
    ))

    await db.commit()
    pending = await absence_service.get_pending_days(user_id, year, db)
    company = await db.get(Company, current_user.company_id)
    expires_at, expired = _carryover_expiry(company, year) if company else (None, False)
    return VacationBalanceOut.model_validate(balance).model_copy(update={
        "pending_days": pending,
        "available_days": absence_service.effective_available(balance, expired),
        "carried_over_expires_at": expires_at,
        "carried_over_expired": expired,
    })
