"""HR API v1 — Leave management endpoints (CBV).

Covers: LeaveType, PublicHoliday, LeaveRequest (CRUD + state transitions),
LeaveBalance (list + allocate), LeaveCalendar.
"""
from __future__ import annotations

from datetime import date as _date

from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.hr_core import selectors as sel
from simorgh.apps.hr_core import services
from simorgh.apps.hr_core.permissions import (
    PERM_LEAVE_APPROVE,
    PERM_LEAVE_MANAGE,
    PERM_LEAVE_REQUEST,
)
from simorgh.apps.hr_core.serializers import (
    LeaveBalanceSerializer,
    LeaveRequestSerializer,
    LeaveTypeSerializer,
    PublicHolidaySerializer,
)
from simorgh.core.api.mixins import PermissionCheckMixin
from simorgh.core.api.views import TenantAPIView, TenantListCreateView


# ---------------------------------------------------------------------------
# Leave Types
# ---------------------------------------------------------------------------

class LeaveTypeListCreateView(PermissionCheckMixin, TenantListCreateView):
    """GET/POST /hr/leave-types/"""

    def get_required_permission(self, request: Request) -> str | None:
        return PERM_LEAVE_MANAGE if request.method == "POST" else PERM_LEAVE_REQUEST

    def get(self, request: Request, *args, **kwargs) -> Response:
        tenant = self.get_tenant()
        if tenant is None:
            return self._no_tenant()
        return Response([self.serialize_object(obj) for obj in self.get_queryset()])

    def get_queryset(self):
        return sel.leave_types_for_tenant(self.tenant.pk)

    def serialize_object(self, obj) -> dict:
        return LeaveTypeSerializer(obj).data

    def create_object(self, data: dict):
        if not data.get("name") or not data.get("code"):
            raise ValueError("name and code are required.")
        return services.create_leave_type(
            tenant_id=self.tenant.pk,
            name=data["name"],
            code=data["code"],
            color=data.get("color", "#4A90D9"),
            is_paid=bool(data.get("is_paid", True)),
            requires_approval=bool(data.get("requires_approval", True)),
            max_days_per_year=data.get("max_days_per_year"),
            carry_forward_days=data.get("carry_forward_days", 0),
            accrual_method=data.get("accrual_method", "upfront"),
        )


class LeaveTypeDetailView(PermissionCheckMixin, TenantAPIView):
    """GET/PATCH/DELETE /hr/leave-types/<pk>/"""

    def get_required_permission(self, request: Request) -> str | None:
        return PERM_LEAVE_MANAGE if request.method != "GET" else PERM_LEAVE_REQUEST

    def _get_object(self, pk: int):
        return sel.leave_type_by_id(self.tenant.pk, pk)

    def get(self, request: Request, pk: int) -> Response:
        lt = self._get_object(pk)
        if lt is None:
            return self._not_found("LeaveType")
        return Response(LeaveTypeSerializer(lt).data)

    def patch(self, request: Request, pk: int) -> Response:
        lt = self._get_object(pk)
        if lt is None:
            return self._not_found("LeaveType")
        data = request.data
        try:
            lt = services.update_leave_type(
                lt,
                name=data.get("name"),
                code=data.get("code"),
                color=data.get("color"),
                is_paid=data.get("is_paid"),
                requires_approval=data.get("requires_approval"),
                max_days_per_year=data.get("max_days_per_year"),
                carry_forward_days=data.get("carry_forward_days"),
                accrual_method=data.get("accrual_method"),
            )
        except Exception as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(LeaveTypeSerializer(lt).data)

    def delete(self, request: Request, pk: int) -> Response:
        lt = self._get_object(pk)
        if lt is None:
            return self._not_found("LeaveType")
        services.delete_leave_type(lt)
        return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Public Holidays
# ---------------------------------------------------------------------------

class PublicHolidayListCreateView(PermissionCheckMixin, TenantListCreateView):
    """GET/POST /hr/public-holidays/"""

    def get_required_permission(self, request: Request) -> str | None:
        return PERM_LEAVE_MANAGE if request.method == "POST" else PERM_LEAVE_REQUEST

    def get_queryset(self):
        year = self.request.query_params.get("year")
        return sel.public_holidays_for_tenant(
            self.tenant.pk, year=int(year) if year else None
        )

    def serialize_object(self, obj) -> dict:
        return PublicHolidaySerializer(obj).data

    def create_object(self, data: dict):
        if not data.get("date") or not data.get("name"):
            raise ValueError("date and name are required.")
        return services.create_public_holiday(
            tenant_id=self.tenant.pk,
            date=_date.fromisoformat(str(data["date"])),
            name=data["name"],
            calendar_type=data.get("calendar_type", "gregorian"),
            is_recurring=bool(data.get("is_recurring", False)),
        )


class PublicHolidayDetailView(PermissionCheckMixin, TenantAPIView):
    """DELETE /hr/public-holidays/<pk>/"""

    permission_required = PERM_LEAVE_MANAGE

    def delete(self, request: Request, pk: int) -> Response:
        from simorgh.apps.hr_core.models import PublicHoliday
        try:
            h = PublicHoliday.objects.get(tenant=self.tenant, pk=pk)
        except PublicHoliday.DoesNotExist:
            return self._not_found("PublicHoliday")
        services.delete_public_holiday(h)
        return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Leave Requests
# ---------------------------------------------------------------------------

class LeaveRequestListCreateView(PermissionCheckMixin, TenantListCreateView):
    """GET/POST /hr/leave-requests/"""

    permission_required = PERM_LEAVE_REQUEST

    def get(self, request: Request, *args, **kwargs) -> Response:
        tenant = self.get_tenant()
        if tenant is None:
            return self._no_tenant()
        return Response([self.serialize_object(obj) for obj in self.get_queryset()])

    def get_queryset(self):
        qp = self.request.query_params
        return sel.leave_requests(
            self.tenant.pk,
            employee_id=int(qp["employee"]) if qp.get("employee") else None,
            leave_type_id=int(qp["leave_type"]) if qp.get("leave_type") else None,
            status=qp.get("status"),
            from_date=qp.get("from_date"),
            to_date=qp.get("to_date"),
        )

    def serialize_object(self, obj) -> dict:
        return LeaveRequestSerializer(obj).data

    def create_object(self, data: dict):
        required = ("employee_id", "leave_type_id", "from_date", "to_date")
        missing = [f for f in required if not data.get(f)]
        if missing:
            raise ValueError(f"Required fields missing: {', '.join(missing)}")

        from simorgh.apps.hr_core.models import Employee, LeaveType
        try:
            employee = Employee.objects.get(tenant=self.tenant, pk=data["employee_id"])
        except Employee.DoesNotExist:
            raise ValueError("Employee not found.")
        try:
            leave_type = LeaveType.objects.get(tenant=self.tenant, pk=data["leave_type_id"])
        except LeaveType.DoesNotExist:
            raise ValueError("LeaveType not found.")

        return services.create_leave_request(
            tenant_id=self.tenant.pk,
            employee=employee,
            leave_type=leave_type,
            from_date=_date.fromisoformat(str(data["from_date"])),
            to_date=_date.fromisoformat(str(data["to_date"])),
            reason=data.get("reason", ""),
        )


class LeaveRequestDetailView(PermissionCheckMixin, TenantAPIView):
    """GET/PATCH/DELETE /hr/leave-requests/<public_id>/"""

    def get_required_permission(self, request: Request) -> str | None:
        if request.method == "DELETE":
            return PERM_LEAVE_MANAGE
        return PERM_LEAVE_REQUEST

    def _get_object(self, public_id):
        import uuid
        try:
            uid = uuid.UUID(str(public_id))
        except ValueError:
            return None
        return sel.leave_request_by_public_id(self.tenant.pk, uid)

    def get(self, request: Request, public_id) -> Response:
        req = self._get_object(public_id)
        if req is None:
            return self._not_found("LeaveRequest")
        return Response(LeaveRequestSerializer(req).data)

    def patch(self, request: Request, public_id) -> Response:
        req = self._get_object(public_id)
        if req is None:
            return self._not_found("LeaveRequest")
        from simorgh.apps.hr_core.models import LeaveRequestStatus
        if req.status != LeaveRequestStatus.DRAFT:
            return Response(
                {"detail": "Only draft requests can be edited."},
                status=status.HTTP_400_BAD_REQUEST,
            )
        data = request.data
        if data.get("reason") is not None:
            req.reason = data["reason"]
            req.save()
        return Response(LeaveRequestSerializer(req).data)

    def delete(self, request: Request, public_id) -> Response:
        req = self._get_object(public_id)
        if req is None:
            return self._not_found("LeaveRequest")
        req.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


class LeaveRequestSubmitView(PermissionCheckMixin, TenantAPIView):
    """POST /hr/leave-requests/<public_id>/submit/"""

    permission_required = PERM_LEAVE_REQUEST

    def post(self, request: Request, public_id) -> Response:
        import uuid
        try:
            uid = uuid.UUID(str(public_id))
        except ValueError:
            return self._not_found("LeaveRequest")
        req = sel.leave_request_by_public_id(self.tenant.pk, uid)
        if req is None:
            return self._not_found("LeaveRequest")
        try:
            req = services.submit_leave_request(req)
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(LeaveRequestSerializer(req).data)


class LeaveRequestApproveView(PermissionCheckMixin, TenantAPIView):
    """POST /hr/leave-requests/<public_id>/approve/"""

    permission_required = PERM_LEAVE_APPROVE

    def post(self, request: Request, public_id) -> Response:
        import uuid
        try:
            uid = uuid.UUID(str(public_id))
        except ValueError:
            return self._not_found("LeaveRequest")
        req = sel.leave_request_by_public_id(self.tenant.pk, uid)
        if req is None:
            return self._not_found("LeaveRequest")
        try:
            req = services.approve_leave_request(
                req, reviewed_by=request.user, note=request.data.get("note", "")
            )
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(LeaveRequestSerializer(req).data)


class LeaveRequestRejectView(PermissionCheckMixin, TenantAPIView):
    """POST /hr/leave-requests/<public_id>/reject/"""

    permission_required = PERM_LEAVE_APPROVE

    def post(self, request: Request, public_id) -> Response:
        import uuid
        try:
            uid = uuid.UUID(str(public_id))
        except ValueError:
            return self._not_found("LeaveRequest")
        req = sel.leave_request_by_public_id(self.tenant.pk, uid)
        if req is None:
            return self._not_found("LeaveRequest")
        try:
            req = services.reject_leave_request(
                req, reviewed_by=request.user, note=request.data.get("note", "")
            )
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(LeaveRequestSerializer(req).data)


class LeaveRequestCancelView(PermissionCheckMixin, TenantAPIView):
    """POST /hr/leave-requests/<public_id>/cancel/"""

    permission_required = PERM_LEAVE_REQUEST

    def post(self, request: Request, public_id) -> Response:
        import uuid
        try:
            uid = uuid.UUID(str(public_id))
        except ValueError:
            return self._not_found("LeaveRequest")
        req = sel.leave_request_by_public_id(self.tenant.pk, uid)
        if req is None:
            return self._not_found("LeaveRequest")
        try:
            req = services.cancel_leave_request(req)
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(LeaveRequestSerializer(req).data)


# ---------------------------------------------------------------------------
# Leave Balances
# ---------------------------------------------------------------------------

class LeaveBalanceListView(PermissionCheckMixin, TenantListCreateView):
    """GET /hr/leave-balances/"""

    permission_required = PERM_LEAVE_REQUEST

    def get(self, request: Request, *args, **kwargs) -> Response:
        tenant = self.get_tenant()
        if tenant is None:
            return self._no_tenant()
        return Response([self.serialize_object(obj) for obj in self.get_queryset()])

    def get_queryset(self):
        qp = self.request.query_params
        return sel.leave_balances(
            self.tenant.pk,
            employee_id=int(qp["employee"]) if qp.get("employee") else None,
            year=int(qp["year"]) if qp.get("year") else None,
            leave_type_id=int(qp["leave_type"]) if qp.get("leave_type") else None,
        )

    def serialize_object(self, obj) -> dict:
        return LeaveBalanceSerializer(obj).data

    def create_object(self, data: dict):
        raise NotImplementedError  # POST handled by separate allocate endpoint


class LeaveBalanceAllocateView(PermissionCheckMixin, TenantAPIView):
    """POST /hr/leave-balances/allocate/"""

    permission_required = PERM_LEAVE_MANAGE

    def post(self, request: Request) -> Response:
        data = request.data
        required = ("employee_id", "leave_type_id", "year", "days")
        missing = [f for f in required if data.get(f) is None]
        if missing:
            return Response(
                {f: ["Required."] for f in missing},
                status=status.HTTP_400_BAD_REQUEST,
            )
        from simorgh.apps.hr_core.models import Employee, LeaveType
        try:
            employee = Employee.objects.get(tenant=self.tenant, pk=data["employee_id"])
        except Employee.DoesNotExist:
            return self._not_found("Employee")
        try:
            leave_type = LeaveType.objects.get(tenant=self.tenant, pk=data["leave_type_id"])
        except LeaveType.DoesNotExist:
            return self._not_found("LeaveType")
        try:
            bal = services.allocate_leave_balance(
                employee=employee,
                leave_type=leave_type,
                year=int(data["year"]),
                days=data["days"],
            )
        except Exception as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(LeaveBalanceSerializer(bal).data, status=status.HTTP_201_CREATED)


class LeaveCalendarView(PermissionCheckMixin, TenantAPIView):
    """GET /hr/leave-calendar/"""

    permission_required = PERM_LEAVE_REQUEST

    def get(self, request: Request) -> Response:
        qp = request.query_params
        from_date_str = qp.get("from")
        to_date_str = qp.get("to")
        if not from_date_str or not to_date_str:
            return Response(
                {"detail": "from and to query params are required."},
                status=status.HTTP_400_BAD_REQUEST,
            )
        try:
            fd = _date.fromisoformat(from_date_str)
            td = _date.fromisoformat(to_date_str)
        except ValueError:
            return Response(
                {"detail": "Invalid date format. Use YYYY-MM-DD."},
                status=status.HTTP_400_BAD_REQUEST,
            )
        dept = int(qp["department"]) if qp.get("department") else None
        qs = sel.leave_calendar(self.tenant.pk, from_date=fd, to_date=td, department_id=dept)
        return Response(LeaveRequestSerializer(qs, many=True).data)
