"""HR API v1 — Employee endpoints.

CBV-based views that replace the legacy function-based employee views.
Uses TenantListCreateView / TenantRetrieveUpdateDestroyView from core.api.
"""
from __future__ import annotations

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_DOCUMENT_MANAGE,
    PERM_EMPLOYEE_CREATE,
    PERM_EMPLOYEE_SENSITIVE_VIEW,
    PERM_EMPLOYEE_TERMINATE,
    PERM_EMPLOYEE_UPDATE,
    PERM_EMPLOYEE_VIEW,
    PERM_JOB_TITLE_MANAGE,
)
from simorgh.apps.hr_core.serializers import (
    EmployeeBankAccountSerializer,
    EmployeeDocumentSerializer,
    EmployeeSerializer,
    JobTitleSerializer,
)
from simorgh.core.api.mixins import PermissionCheckMixin
from simorgh.core.api.views import TenantAPIView, TenantListCreateView, TenantRetrieveUpdateDestroyView


# ---------------------------------------------------------------------------
# Job Titles
# ---------------------------------------------------------------------------

class JobTitleListCreateView(PermissionCheckMixin, TenantListCreateView):
    """GET  /hr/job-titles/   — list
    POST /hr/job-titles/   — create
    """

    def get_required_permission(self, request: Request) -> str | None:
        return PERM_JOB_TITLE_MANAGE if request.method == "POST" else PERM_EMPLOYEE_VIEW

    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.job_titles_for_tenant(self.tenant.pk)

    def serialize_object(self, obj) -> dict:
        return JobTitleSerializer(obj).data

    def create_object(self, data: dict):
        title = (data.get("title") or "").strip()
        if not title:
            raise ValueError("title is required.")
        return services.create_job_title(
            tenant_id=self.tenant.pk,
            title=title,
            level=int(data.get("level", 1)),
            department_id=data.get("department_id") or None,
        )


class JobTitleDetailView(PermissionCheckMixin, TenantAPIView):
    """PATCH /hr/job-titles/<pk>/  — update
    DELETE /hr/job-titles/<pk>/ — delete
    """

    permission_required = PERM_JOB_TITLE_MANAGE

    def _get_object(self, pk: int):
        obj = sel.job_title_by_id(self.tenant.pk, pk)
        if obj is None:
            return None
        return obj

    def patch(self, request: Request, pk: int) -> Response:
        jt = self._get_object(pk)
        if jt is None:
            return self._not_found("JobTitle")
        data = request.data
        jt = services.update_job_title(
            jt,
            title=data.get("title"),
            level=data.get("level"),
            department_id=data.get("department_id"),
        )
        return Response(JobTitleSerializer(jt).data)

    def delete(self, request: Request, pk: int) -> Response:
        jt = self._get_object(pk)
        if jt is None:
            return self._not_found("JobTitle")
        jt.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Employees
# ---------------------------------------------------------------------------

class EmployeeListCreateView(PermissionCheckMixin, TenantListCreateView):
    """GET  /hr/employees/  — list (paginated)
    POST /hr/employees/  — create
    """

    def get_required_permission(self, request: Request) -> str | None:
        return PERM_EMPLOYEE_CREATE if request.method == "POST" else PERM_EMPLOYEE_VIEW

    def _show_sensitive(self) -> bool:
        from simorgh.apps.iam.engine import is_allowed
        from simorgh.core.context import current_request_context
        ctx = current_request_context()
        return is_allowed(ctx, PERM_EMPLOYEE_SENSITIVE_VIEW)

    def get_queryset(self):
        qp = self.request.query_params
        return sel.employees_for_tenant(
            self.tenant.pk,
            status=qp.get("status"),
            department_id=int(qp["department"]) if qp.get("department") else None,
            employment_type=qp.get("employment_type"),
            manager_id=int(qp["manager"]) if qp.get("manager") else None,
            job_title_id=int(qp["job_title"]) if qp.get("job_title") else None,
            search=qp.get("q"),
        )

    def serialize_object(self, obj) -> dict:
        return EmployeeSerializer(obj, context={"show_sensitive": self._show_sensitive()}).data

    def create_object(self, data: dict):
        from datetime import date
        org_node_id = data.get("organization_node_id") or self._default_org_node()
        if not org_node_id:
            raise ValueError("organization_node_id is required.")

        required = ("first_name", "last_name", "hire_date")
        missing = [f for f in required if not data.get(f)]
        if missing:
            raise ValueError(f"Required fields missing: {', '.join(missing)}")

        try:
            hire_date = date.fromisoformat(str(data["hire_date"]))
        except (ValueError, TypeError):
            raise ValueError("hire_date: enter a valid date (YYYY-MM-DD).")

        return services.create_employee(
            tenant_id=self.tenant.pk,
            organization_node_id=int(org_node_id),
            first_name=data["first_name"],
            last_name=data["last_name"],
            hire_date=hire_date,
            employment_type=data.get("employment_type", "full_time"),
            user_id=data.get("user_id") or None,
            display_name=data.get("display_name", ""),
            national_id=data.get("national_id", ""),
            birth_date=date.fromisoformat(str(data["birth_date"])) if data.get("birth_date") else None,
            gender=data.get("gender", ""),
            mobile=data.get("mobile", ""),
            personal_email=data.get("personal_email", ""),
            work_email=data.get("work_email", ""),
            status=data.get("status", "active"),
            department_id=data.get("department_id") or None,
            manager_id=data.get("manager_id") or None,
            job_title_id=data.get("job_title_id") or None,
            work_location=data.get("work_location", ""),
        )

    def _default_org_node(self) -> int | None:
        try:
            from simorgh.apps.organizations.models import OrganizationNode
            node = OrganizationNode.objects.filter(
                tenant=self.tenant
            ).order_by("depth", "path_string").first()
            return node.pk if node else None
        except Exception:
            return None


class EmployeeDetailView(PermissionCheckMixin, TenantAPIView):
    """GET   /hr/employees/<public_id>/   — retrieve
    PATCH /hr/employees/<public_id>/   — update
    DELETE /hr/employees/<public_id>/  — soft-delete
    """

    permission_required = PERM_EMPLOYEE_VIEW

    def _get_object(self, public_id):
        from simorgh.apps.hr_core.models import Employee
        try:
            return Employee.objects.get(tenant=self.tenant, public_id=public_id)
        except Employee.DoesNotExist:
            return None

    def _show_sensitive(self) -> bool:
        from simorgh.apps.iam.engine import is_allowed
        from simorgh.core.context import current_request_context
        return is_allowed(current_request_context(), PERM_EMPLOYEE_SENSITIVE_VIEW)

    def get(self, request: Request, public_id) -> Response:
        emp = self._get_object(public_id)
        if emp is None:
            return self._not_found("Employee")
        return Response(EmployeeSerializer(emp, context={"show_sensitive": self._show_sensitive()}).data)

    def patch(self, request: Request, public_id) -> Response:
        from simorgh.apps.iam.engine import is_allowed
        from simorgh.core.context import current_request_context
        if not is_allowed(current_request_context(), PERM_EMPLOYEE_UPDATE):
            return self._forbidden(f"Permission '{PERM_EMPLOYEE_UPDATE}' is required.")

        emp = self._get_object(public_id)
        if emp is None:
            return self._not_found("Employee")

        data = request.data
        from datetime import date
        kwargs: dict = {}
        for field in (
            "first_name", "last_name", "display_name", "national_id",
            "gender", "mobile", "personal_email", "work_email",
            "employment_type", "status", "work_location",
        ):
            if field in data:
                kwargs[field] = data[field]

        for date_field in ("hire_date", "birth_date"):
            if date_field in data and data[date_field]:
                try:
                    kwargs[date_field] = date.fromisoformat(str(data[date_field]))
                except ValueError:
                    return Response(
                        {date_field: ["Enter a valid date (YYYY-MM-DD)."]},
                        status=status.HTTP_400_BAD_REQUEST,
                    )

        for id_field in ("department_id", "manager_id", "job_title_id"):
            if id_field in data:
                kwargs[id_field] = int(data[id_field]) if data[id_field] else None

        try:
            emp = services.update_employee(emp, **kwargs)
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

        return Response(EmployeeSerializer(emp, context={"show_sensitive": self._show_sensitive()}).data)

    def delete(self, request: Request, public_id) -> Response:
        from simorgh.apps.iam.engine import is_allowed
        from simorgh.core.context import current_request_context
        if not is_allowed(current_request_context(), PERM_EMPLOYEE_TERMINATE):
            return self._forbidden(f"Permission '{PERM_EMPLOYEE_TERMINATE}' is required.")

        emp = self._get_object(public_id)
        if emp is None:
            return self._not_found("Employee")

        emp.delete()  # soft-delete via SoftDeleteModel
        return Response(status=status.HTTP_204_NO_CONTENT)


class EmployeeTerminateView(PermissionCheckMixin, TenantAPIView):
    """POST /hr/employees/<public_id>/terminate/"""

    permission_required = PERM_EMPLOYEE_TERMINATE

    def post(self, request: Request, public_id) -> Response:
        from datetime import date
        from simorgh.apps.hr_core.models import Employee
        try:
            emp = Employee.objects.get(tenant=self.tenant, public_id=public_id)
        except Employee.DoesNotExist:
            return self._not_found("Employee")

        data = request.data
        if not data.get("termination_date"):
            return Response(
                {"termination_date": ["This field is required."]},
                status=status.HTTP_400_BAD_REQUEST,
            )
        try:
            termination_date = date.fromisoformat(str(data["termination_date"]))
        except ValueError:
            return Response(
                {"termination_date": ["Enter a valid date (YYYY-MM-DD)."]},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            emp = services.terminate_employee(
                emp,
                termination_date=termination_date,
                reason=data.get("reason", ""),
            )
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

        return Response(EmployeeSerializer(emp, context={"show_sensitive": False}).data)


class EmployeeMeView(TenantAPIView):
    """GET/PATCH /hr/employees/me/ — employee self-service."""

    def _get_employee(self):
        from simorgh.apps.hr_core.models import Employee
        try:
            return Employee.objects.get(tenant=self.tenant, user_id=self.request.user.pk)
        except Employee.DoesNotExist:
            return None

    def get(self, request: Request) -> Response:
        emp = self._get_employee()
        if emp is None:
            return self._not_found("Employee")
        return Response(EmployeeSerializer(emp, context={"show_sensitive": True}).data)

    def patch(self, request: Request) -> Response:
        from simorgh.apps.app_settings.services import resolve as resolve_setting
        allow_ess = resolve_setting("hr.allow_self_service_update", tenant_id=self.tenant.pk)
        if not allow_ess:
            return self._forbidden("Self-service updates are disabled by your administrator.")

        emp = self._get_employee()
        if emp is None:
            return self._not_found("Employee")

        allowed_fields = {"mobile", "personal_email", "display_name", "work_location", "gender"}
        kwargs = {k: v for k, v in request.data.items() if k in allowed_fields}
        if not kwargs:
            return Response(
                {"detail": "No updatable fields provided."},
                status=status.HTTP_400_BAD_REQUEST,
            )
        try:
            emp = services.update_employee(emp, **kwargs)
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

        return Response(EmployeeSerializer(emp, context={"show_sensitive": True}).data)


class EmployeeDirectReportsView(PermissionCheckMixin, TenantAPIView):
    """GET /hr/employees/<public_id>/direct-reports/"""

    permission_required = PERM_EMPLOYEE_VIEW

    def get(self, request: Request, public_id) -> Response:
        from simorgh.apps.hr_core.models import Employee
        try:
            emp = Employee.objects.get(tenant=self.tenant, public_id=public_id)
        except Employee.DoesNotExist:
            return self._not_found("Employee")
        reports = sel.direct_reports(emp)
        return Response(EmployeeSerializer(reports, many=True).data)


class OrgChartView(PermissionCheckMixin, TenantAPIView):
    """GET /hr/org-chart/

    Optional query param: ``depth=<int>`` — limits the number of levels
    returned.  Useful for large tenants; defaults to unlimited.
    """

    permission_required = PERM_EMPLOYEE_VIEW

    def get(self, request: Request) -> Response:
        raw_depth = request.query_params.get("depth")
        max_depth: int | None = None
        if raw_depth is not None:
            try:
                max_depth = max(1, int(raw_depth))
            except (TypeError, ValueError):
                pass
        tree = sel.org_chart_tree(self.tenant.pk, max_depth=max_depth)
        return Response(tree)


# ---------------------------------------------------------------------------
# Departments proxy endpoint
# ---------------------------------------------------------------------------

class DepartmentListView(PermissionCheckMixin, TenantAPIView):
    """GET /hr/departments/ — list org nodes as department options.

    Returns all active OrganizationNode records for the tenant so that
    relation-picker widgets in the frontend can populate department selectors.
    """

    permission_required = PERM_EMPLOYEE_VIEW

    def get(self, request: Request) -> Response:
        from simorgh.apps.organizations.models import OrganizationNode
        nodes = OrganizationNode.objects.filter(
            tenant=self.tenant,
            is_active=True,
        ).order_by("depth", "name").values("id", "name", "type", "parent_id")
        return Response(list(nodes))


# ---------------------------------------------------------------------------
# Employee Documents
# ---------------------------------------------------------------------------

class EmployeeDocumentListCreateView(PermissionCheckMixin, TenantListCreateView):
    """GET/POST /hr/employees/<public_id>/documents/"""

    def get_required_permission(self, request: Request) -> str | None:
        return PERM_DOCUMENT_MANAGE if request.method == "POST" else PERM_EMPLOYEE_VIEW

    def _get_employee(self, public_id):
        from simorgh.apps.hr_core.models import Employee
        try:
            return Employee.objects.get(tenant=self.tenant, public_id=public_id)
        except Employee.DoesNotExist:
            return None

    def get(self, request: Request, public_id) -> Response:
        emp = self._get_employee(public_id)
        if emp is None:
            return self._not_found("Employee")
        docs = sel.documents_for_employee(emp)
        return Response(EmployeeDocumentSerializer(docs, many=True).data)

    def post(self, request: Request, public_id) -> Response:
        from simorgh.apps.iam.engine import is_allowed
        from simorgh.core.context import current_request_context
        if not is_allowed(current_request_context(), PERM_DOCUMENT_MANAGE):
            return self._forbidden(f"Permission '{PERM_DOCUMENT_MANAGE}' is required.")

        emp = self._get_employee(public_id)
        if emp is None:
            return self._not_found("Employee")

        data = request.data
        if not data.get("dms_file_id"):
            return Response(
                {"dms_file_id": ["This field is required."]},
                status=status.HTTP_400_BAD_REQUEST,
            )
        from datetime import date
        doc = services.add_employee_document(
            employee=emp,
            doc_type=data.get("doc_type", "other"),
            dms_file_id=str(data["dms_file_id"]),
            title=data.get("title", ""),
            expiry_date=date.fromisoformat(str(data["expiry_date"])) if data.get("expiry_date") else None,
        )
        return Response(EmployeeDocumentSerializer(doc).data, status=status.HTTP_201_CREATED)

    # Satisfy TenantListCreateView abstract requirements (not used — overridden above)
    def get_queryset(self): pass  # type: ignore[override]
    def serialize_object(self, obj): pass  # type: ignore[override]


class EmployeeDocumentDetailView(PermissionCheckMixin, TenantAPIView):
    """PATCH/DELETE /hr/employees/<public_id>/documents/<doc_id>/"""

    permission_required = PERM_DOCUMENT_MANAGE

    def _get_objects(self, public_id, doc_id):
        from simorgh.apps.hr_core.models import Employee, EmployeeDocument
        try:
            emp = Employee.objects.get(tenant=self.tenant, public_id=public_id)
        except Employee.DoesNotExist:
            return None, None
        try:
            doc = EmployeeDocument.objects.get(employee=emp, pk=doc_id)
        except EmployeeDocument.DoesNotExist:
            return emp, None
        return emp, doc

    def patch(self, request: Request, public_id, doc_id: int) -> Response:
        _, doc = self._get_objects(public_id, doc_id)
        if doc is None:
            return self._not_found("Document")
        data = request.data
        if data.get("is_verified"):
            services.verify_employee_document(doc)
        if "title" in data:
            doc.title = data["title"]
            doc.save()
        return Response(EmployeeDocumentSerializer(doc).data)

    def delete(self, request: Request, public_id, doc_id: int) -> Response:
        _, doc = self._get_objects(public_id, doc_id)
        if doc is None:
            return self._not_found("Document")
        services.delete_employee_document(doc)
        return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Employee Bank Accounts
# ---------------------------------------------------------------------------

class EmployeeBankAccountListCreateView(PermissionCheckMixin, TenantAPIView):
    """GET/POST /hr/employees/<public_id>/bank-accounts/"""

    permission_required = PERM_EMPLOYEE_VIEW

    def _get_employee(self, public_id):
        from simorgh.apps.hr_core.models import Employee
        try:
            return Employee.objects.get(tenant=self.tenant, public_id=public_id)
        except Employee.DoesNotExist:
            return None

    def _show_sensitive(self) -> bool:
        from simorgh.apps.iam.engine import is_allowed
        from simorgh.core.context import current_request_context
        return is_allowed(current_request_context(), PERM_EMPLOYEE_SENSITIVE_VIEW)

    def _feature_guard(self) -> Response | None:
        from simorgh.apps.modules.selectors import is_feature_enabled
        if not is_feature_enabled(self.tenant.pk, "hr.sensitive_data"):
            return Response(
                {"detail": "Feature not available.", "feature": "hr.sensitive_data"},
                status=status.HTTP_403_FORBIDDEN,
            )
        return None

    def get(self, request: Request, public_id) -> Response:
        denied = self._feature_guard()
        if denied:
            return denied
        emp = self._get_employee(public_id)
        if emp is None:
            return self._not_found("Employee")
        accounts = sel.bank_accounts_for_employee(emp)
        return Response(
            EmployeeBankAccountSerializer(
                accounts, many=True, context={"show_sensitive": self._show_sensitive()}
            ).data
        )

    def post(self, request: Request, public_id) -> Response:
        denied = self._feature_guard()
        if denied:
            return denied
        from simorgh.apps.iam.engine import is_allowed
        from simorgh.core.context import current_request_context
        if not is_allowed(current_request_context(), PERM_EMPLOYEE_UPDATE):
            return self._forbidden(f"Permission '{PERM_EMPLOYEE_UPDATE}' is required.")

        emp = self._get_employee(public_id)
        if emp is None:
            return self._not_found("Employee")

        data = request.data
        missing = [f for f in ("bank_name", "account_no") if not data.get(f)]
        if missing:
            return Response(
                {f: ["This field is required."] for f in missing},
                status=status.HTTP_400_BAD_REQUEST,
            )
        account = services.add_bank_account(
            employee=emp,
            bank_name=data["bank_name"],
            account_no=data["account_no"],
            iban=data.get("iban", ""),
            is_default=bool(data.get("is_default", False)),
        )
        return Response(
            EmployeeBankAccountSerializer(account, context={"show_sensitive": self._show_sensitive()}).data,
            status=status.HTTP_201_CREATED,
        )


class EmployeeBankAccountDetailView(PermissionCheckMixin, TenantAPIView):
    """PATCH/DELETE /hr/employees/<public_id>/bank-accounts/<account_id>/"""

    permission_required = PERM_EMPLOYEE_UPDATE

    def _feature_guard(self) -> Response | None:
        from simorgh.apps.modules.selectors import is_feature_enabled
        if not is_feature_enabled(self.tenant.pk, "hr.sensitive_data"):
            return Response(
                {"detail": "Feature not available.", "feature": "hr.sensitive_data"},
                status=status.HTTP_403_FORBIDDEN,
            )
        return None

    def _get_account(self, public_id, account_id: int):
        from simorgh.apps.hr_core.models import Employee, EmployeeBankAccount
        try:
            emp = Employee.objects.get(tenant=self.tenant, public_id=public_id)
        except Employee.DoesNotExist:
            return None
        try:
            return EmployeeBankAccount.objects.get(employee=emp, pk=account_id)
        except EmployeeBankAccount.DoesNotExist:
            return None

    def patch(self, request: Request, public_id, account_id: int) -> Response:
        denied = self._feature_guard()
        if denied:
            return denied
        account = self._get_account(public_id, account_id)
        if account is None:
            return self._not_found("BankAccount")
        data = request.data
        account = services.update_bank_account(
            account,
            bank_name=data.get("bank_name"),
            account_no=data.get("account_no"),
            iban=data.get("iban"),
            is_default=data.get("is_default"),
        )
        show = False  # Don't reveal by default on update response
        return Response(EmployeeBankAccountSerializer(account, context={"show_sensitive": show}).data)

    def delete(self, request: Request, public_id, account_id: int) -> Response:
        denied = self._feature_guard()
        if denied:
            return denied
        account = self._get_account(public_id, account_id)
        if account is None:
            return self._not_found("BankAccount")
        services.delete_bank_account(account)
        return Response(status=status.HTTP_204_NO_CONTENT)
