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

from app.database import get_db
from app.deps import require_permission
from app.models import Employee, EmploymentType, Event, StaffAllocation, User
from app.schemas import AllocationIn, AllocationOut, EmployeeIn, EmployeeOut, EmploymentTypeIn, EmploymentTypeOut
from app.utils import money, next_public_id

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


@router.get("/employment-types", response_model=list[EmploymentTypeOut])
def list_employment_types(db: Session = Depends(get_db), _: User = Depends(require_permission("staff.view"))):
    return db.query(EmploymentType).order_by(EmploymentType.name.asc()).all()


@router.get("/employment-types/{type_id}", response_model=EmploymentTypeOut)
def get_employment_type(type_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.view"))):
    row = db.query(EmploymentType).filter(EmploymentType.id == type_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Employment type not found")
    return row


@router.post("/employment-types", response_model=EmploymentTypeOut)
def create_employment_type(payload: EmploymentTypeIn, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.create"))):
    existing = db.query(EmploymentType).filter(EmploymentType.name == payload.name.strip()).first()
    if existing is not None:
        raise HTTPException(status_code=400, detail="Employment type already exists")
    row = EmploymentType(name=payload.name.strip(), is_active=payload.is_active)
    db.add(row)
    db.commit()
    db.refresh(row)
    return row


@router.patch("/employment-types/{type_id}", response_model=EmploymentTypeOut)
def update_employment_type(
    type_id: int,
    payload: EmploymentTypeIn,
    db: Session = Depends(get_db),
    _: User = Depends(require_permission("staff.edit")),
):
    row = db.query(EmploymentType).filter(EmploymentType.id == type_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Employment type not found")
    name = payload.name.strip()
    clash = db.query(EmploymentType).filter(EmploymentType.name == name, EmploymentType.id != type_id).first()
    if clash is not None:
        raise HTTPException(status_code=400, detail="Employment type already exists")
    row.name = name
    row.is_active = payload.is_active
    db.commit()
    db.refresh(row)
    return row


@router.delete("/employment-types/{type_id}")
def delete_employment_type(type_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.edit"))):
    row = db.query(EmploymentType).filter(EmploymentType.id == type_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Employment type not found")
    in_use = db.query(Employee).filter(Employee.employment_type == row.name).first()
    if in_use is not None:
        raise HTTPException(status_code=400, detail="Employment type is used by staff members")
    db.delete(row)
    db.commit()
    return {"ok": True}


def serialize_allocation(row: StaffAllocation) -> AllocationOut:
    data = AllocationOut.model_validate(row)
    data.employee_name = row.employee.name if row.employee else None
    data.event_name = row.event.name if row.event else None
    return data


@router.get("/staff", response_model=list[EmployeeOut])
def list_staff(db: Session = Depends(get_db), _: User = Depends(require_permission("staff.view"))):
    return db.query(Employee).order_by(Employee.name.asc()).all()


@router.get("/staff/{employee_id}", response_model=EmployeeOut)
def get_staff(employee_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.view"))):
    row = db.query(Employee).filter(Employee.id == employee_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Staff not found")
    return row


@router.post("/staff", response_model=EmployeeOut)
def create_staff(payload: EmployeeIn, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.create"))):
    row = Employee(public_id=next_public_id(db, Employee, "EMP"), **payload.model_dump())
    db.add(row)
    db.commit()
    db.refresh(row)
    return row


@router.api_route("/staff/{employee_id}", methods=["PATCH", "PUT"], response_model=EmployeeOut)
def update_staff(employee_id: int, payload: EmployeeIn, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.edit"))):
    row = db.query(Employee).filter(Employee.id == employee_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Staff not found")
    updates = payload.model_dump()
    if "is_active" not in getattr(payload, "model_fields_set", set()):
        updates.pop("is_active", None)
    for key, value in updates.items():
        setattr(row, key, value)
    db.commit()
    db.refresh(row)
    return row


def _overlaps(db: Session, employee_id: int, event: Event, ignore_id: int | None = None) -> bool:
    query = (
        db.query(StaffAllocation)
        .join(Event, Event.id == StaffAllocation.event_id)
        .filter(
            StaffAllocation.employee_id == employee_id,
            StaffAllocation.status.in_(["Assigned", "Confirmed"]),
            Event.status.in_(["Confirmed", "Upcoming", "In Progress"]),
            Event.event_date == event.event_date,
        )
    )
    if ignore_id:
        query = query.filter(StaffAllocation.id != ignore_id)
    for allocation in query.all():
        other = allocation.event
        if event.start_time and event.end_time and other.start_time and other.end_time:
            if event.start_time < other.end_time and event.end_time > other.start_time:
                return True
        else:
            return True
    return False


@router.get("/allocations", response_model=list[AllocationOut])
def list_allocations(event_id: int | None = None, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.view"))):
    query = db.query(StaffAllocation)
    if event_id:
        query = query.filter(StaffAllocation.event_id == event_id)
    return [serialize_allocation(row) for row in query.order_by(StaffAllocation.id.desc()).all()]


@router.get("/allocations/{allocation_id}", response_model=AllocationOut)
def get_allocation(allocation_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.view"))):
    row = db.query(StaffAllocation).filter(StaffAllocation.id == allocation_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Allocation not found")
    return serialize_allocation(row)


@router.post("/allocations", response_model=AllocationOut)
def create_allocation(payload: AllocationIn, db: Session = Depends(get_db), _: User = Depends(require_permission("event.staff.allocate"))):
    event = db.query(Event).filter(Event.id == payload.event_id).first()
    employee = db.query(Employee).filter(Employee.id == payload.employee_id).first()
    if event is None or employee is None:
        raise HTTPException(status_code=404, detail="Event or staff not found")
    if _overlaps(db, employee.id, event):
        raise HTTPException(status_code=400, detail="Staff already allocated to an overlapping event")
    total = money(payload.event_payment) + money(payload.travel_allowance) + money(payload.other_allowance)
    row = StaffAllocation(
        **payload.model_dump(),
        total_staff_cost=total,
    )
    employee.availability_status = "On Event"
    db.add(row)
    db.commit()
    db.refresh(row)
    return serialize_allocation(row)


@router.patch("/allocations/{allocation_id}", response_model=AllocationOut)
def update_allocation(allocation_id: int, payload: AllocationIn, db: Session = Depends(get_db), _: User = Depends(require_permission("staff.allocate"))):
    row = db.query(StaffAllocation).filter(StaffAllocation.id == allocation_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Allocation not found")
    event = db.query(Event).filter(Event.id == payload.event_id).first()
    if event is None:
        raise HTTPException(status_code=404, detail="Event not found")
    if _overlaps(db, payload.employee_id, event, row.id):
        raise HTTPException(status_code=400, detail="Staff already allocated to an overlapping event")
    for key, value in payload.model_dump().items():
        setattr(row, key, value)
    row.total_staff_cost = money(row.event_payment) + money(row.travel_allowance) + money(row.other_allowance)
    db.commit()
    db.refresh(row)
    return serialize_allocation(row)
