"""Attendance API (module `attendance`), mounted at the legacy `/api/...` paths and at `/api/v1/attendance/...`.

Every view is bound to one organization (X-Organization header, or the user's only organization),
is rejected when the attendance module is disabled for it, requires an explicit permission per HTTP
method (deny by default), and works only on repositories bound to that organization.

Data scope: `attendance.view` / `attendance.create` at scope `self` only ever touch the caller's own
records; reading or editing other people's data needs `attendance.report` / `.update` / `.approve` /
`.manage_settings` at scope `organization` or wider.
"""
from __future__ import annotations

import datetime
from functools import cached_property

from django.contrib.auth import authenticate
from rest_framework import status
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken

from application.use_cases.attendance import AdminEditAttendance, CheckOut
from application.use_cases.auth import CheckIn, CompleteProfile
from application.use_cases.holidays import CreateHoliday, DeleteHoliday
from application.use_cases.leaves import GrantDailyLeave, ReviewLeaveRequest, SubmitLeaveRequest
from application.use_cases.reports import BuildMonthlyReport
from application.use_cases.work_schedule import (
    DeleteWorkScheduleForDate,
    SetDefaultWorkSchedule,
    SetWorkScheduleForDate,
)
from apps.workplace import services as workplace
from apps.access.drf import TenantPermission
from apps.access.models import Role
from apps.organizations import services as org_services
from apps.organizations.models import OrganizationMembership
from infrastructure.repositories import DjangoUserRepository
from interfaces.api.serializers import (
    AdminAttendanceEditSerializer,
    AttendanceSerializer,
    CheckOutSerializer,
    CompleteProfileSerializer,
    CreatePersonnelSerializer,
    GrantLeaveSerializer,
    HolidaySerializer,
    LeaveRequestCreateSerializer,
    LeaveRequestSerializer,
    LoginSerializer,
    MonthlyReportRowSerializer,
    UserSerializer,
    WorkScheduleSerializer,
    WorkScheduleSetSerializer,
)
from interfaces.api.support import auto_check_in, repositories_for, require_member, user_payload

ORG_LEVEL = {m: "organization" for m in ("GET", "POST", "PUT", "PATCH", "DELETE")}


class AttendanceView(APIView):
    """Base class: tenant + module gate. Subclasses declare `permission_map` (and `scope_map`)."""

    permission_classes = [TenantPermission]
    module_code = "attendance"
    allow_implicit_org = True

    @cached_property
    def repos(self):
        return repositories_for(self.request.organization)


# ------------------------------------------------------------------ account level (legacy client)


class LoginView(APIView):
    permission_classes: list = []
    authentication_classes: list = []

    def post(self, request):
        serializer = LoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = authenticate(
            request,
            personnel_code=serializer.validated_data["personnel_code"],
            password=serializer.validated_data["password"],
        )
        if user is None:
            return Response({"detail": "Invalid credentials."}, status=status.HTTP_401_UNAUTHORIZED)

        auto_check_in(request, user)

        refresh = RefreshToken.for_user(user)
        return Response(
            {"access": str(refresh.access_token), "refresh": str(refresh), "user": user_payload(request, user)}
        )


class LogoutView(APIView):
    def post(self, request):
        refresh_token = request.data.get("refresh")
        if refresh_token:
            try:
                RefreshToken(refresh_token).blacklist()
            except Exception:
                pass
        return Response(status=status.HTTP_205_RESET_CONTENT)


class MeView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response(user_payload(request, request.user))


class CompleteProfileView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = CompleteProfileSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = CompleteProfile(DjangoUserRepository()).execute(request.user, serializer.validated_data["name"])
        return Response(user_payload(request, user))


# ------------------------------------------------------------------ self service


class TodayAttendanceView(AttendanceView):
    permission_map = {"GET": "attendance.view"}

    def get(self, request):
        require_member(request)
        today = datetime.date.today()
        attendance = self.repos.attendance.get_for_day(request.user.id, today)
        if attendance is None:
            return Response(
                {"date": today, "check_in": None, "check_out": None, "is_default_checkout": False,
                 "tasks_note": None, "worked": None}
            )
        return Response(AttendanceSerializer(attendance).data)


class _Verified:
    """Result of the location check made before a punch is applied."""

    def __init__(self, request, kind, decision, position):
        self.request, self.kind, self.decision, self.position = request, kind, decision, position

    def record(self, punch_time):
        at = datetime.datetime.combine(datetime.date.today(), punch_time)
        lat, lng, acc = self.position
        return workplace.record_punch(
            self.request.organization, self.request.user, self.request, kind=self.kind, at=at,
            decision=self.decision, latitude=lat, longitude=lng, accuracy=acc,
        )

    def summary(self, punch):
        if punch is None:
            return {"required": False, "status": "approved", "verdict": workplace.Verdict.NOT_REQUIRED}
        return {"required": True, "status": punch.status, "verdict": punch.verdict, "place": punch.place_name}


def _number(value):
    try:
        return None if value in (None, "") else float(value)
    except (TypeError, ValueError):
        raise ValidationError("latitude, longitude and accuracy must be numbers.")


def _verify_place(request, kind) -> _Verified:
    """Evaluate the organization's location policy for this check-in/out (raises when a location is needed)."""
    position = (_number(request.data.get("latitude")), _number(request.data.get("longitude")), _number(request.data.get("accuracy")))
    decision = workplace.evaluate(
        request.organization, request.user, request, kind=kind,
        latitude=position[0], longitude=position[1], accuracy=position[2],
    )
    return _Verified(request, kind, decision, position)


class CheckInView(AttendanceView):
    permission_map = {"POST": "attendance.create"}

    def post(self, request):
        require_member(request)
        today = datetime.date.today()
        existing = self.repos.attendance.get_for_day(request.user.id, today)
        if existing is not None and existing.check_in is not None:  # Only an actual check-in is idempotent.
            return Response(AttendanceSerializer(existing).data)
        place = _verify_place(request, "in")
        CheckIn(self.repos.attendance).execute(request.user)
        attendance = self.repos.attendance.get_for_day(request.user.id, today)
        punch = place.record(attendance.check_in)
        return Response({**AttendanceSerializer(attendance).data, "location": place.summary(punch)})


class CheckOutView(AttendanceView):
    permission_map = {"POST": "attendance.create"}

    def post(self, request):
        require_member(request)
        serializer = CheckOutSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        place = _verify_place(request, "out")
        attendance = CheckOut(self.repos.attendance).execute(request.user, serializer.validated_data["tasks_note"])
        punch = place.record(attendance.check_out)
        return Response({**AttendanceSerializer(attendance).data, "location": place.summary(punch)})


class MonthlyReportView(AttendanceView):
    permission_map = {"GET": "attendance.view"}

    def get(self, request):
        try:
            year = int(request.query_params["year"])
            month = int(request.query_params["month"])
        except (KeyError, ValueError):
            raise ValidationError("year and month query params are required integers.")

        raw_user = request.query_params.get("user_id")
        if raw_user:
            try:
                wanted = int(raw_user)
            except ValueError:
                raise ValidationError("user_id must be an integer.")
        else:
            wanted = request.user.id

        if wanted == request.user.id:
            require_member(request)
            target_id = wanted
        else:
            if not request.tenant.has("attendance.report", "organization"):
                raise PermissionDenied("You may only view your own report.", code="permission_denied")
            target = self.repos.members.get_user(wanted)
            if target is None:
                raise NotFound()
            target_id = target.id

        report = BuildMonthlyReport(
            self.repos.attendance, self.repos.holiday, self.repos.leave, self.repos.schedule
        ).execute(target_id, year, month)
        return Response(
            {
                "user_id": target_id,
                "year": year,
                "month": month,
                "rows": MonthlyReportRowSerializer(report.rows, many=True).data,
                "monthly_total": report.monthly_total,
            }
        )


class LeaveRequestListCreateView(AttendanceView):
    permission_map = {"GET": "attendance.view", "POST": "attendance.create"}

    def get(self, request):
        require_member(request)
        return Response(LeaveRequestSerializer(self.repos.leave.list_for_user(request.user.id), many=True).data)

    def post(self, request):
        require_member(request)
        serializer = LeaveRequestCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data
        leave = SubmitLeaveRequest(self.repos.leave).execute(
            user=request.user, date=data["date"], type=data["type"], category=data["category"],
            start_time=data.get("start_time"), end_time=data.get("end_time"), reason=data.get("reason"),
        )
        return Response(LeaveRequestSerializer(leave).data, status=status.HTTP_201_CREATED)


# ------------------------------------------------------------------ managers: leave review


class AdminLeaveRequestListView(AttendanceView):
    permission_map = {"GET": "attendance.approve"}
    scope_map = ORG_LEVEL

    def get(self, request):
        status_filter = request.query_params.get("status")
        if status_filter == "all":
            status_filter = None
        return Response(LeaveRequestSerializer(self.repos.leave.list_all(status=status_filter), many=True).data)


class _LeaveReviewView(AttendanceView):
    permission_map = {"POST": "attendance.approve"}
    scope_map = ORG_LEVEL
    action = "approve"

    def post(self, request, pk):
        leave = self.repos.leave.get_by_id(pk)
        if leave is None:
            raise NotFound()
        review = ReviewLeaveRequest(self.repos.leave)
        leave = getattr(review, self.action)(leave, request.user, str(request.data.get("note") or ""))
        return Response(LeaveRequestSerializer(leave).data)


class AdminLeaveRequestApproveView(_LeaveReviewView):
    action = "approve"


class AdminLeaveRequestRejectView(_LeaveReviewView):
    action = "reject"


class AdminGrantLeaveView(AttendanceView):
    permission_map = {"POST": "attendance.approve"}
    scope_map = ORG_LEVEL

    def post(self, request):
        serializer = GrantLeaveSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        leave = GrantDailyLeave(self.repos.leave, self.repos.members).execute(
            admin=request.user, **serializer.validated_data
        )
        return Response(LeaveRequestSerializer(leave).data, status=status.HTTP_201_CREATED)


# ------------------------------------------------------------------ managers: settings


class AdminHolidayListCreateView(AttendanceView):
    permission_map = {"GET": "attendance.manage_settings", "POST": "attendance.manage_settings"}
    scope_map = ORG_LEVEL

    def get(self, request):
        return Response(HolidaySerializer(self.repos.holiday.list_all(), many=True).data)

    def post(self, request):
        serializer = HolidaySerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        holiday = CreateHoliday(self.repos.holiday).execute(**serializer.validated_data)
        return Response(HolidaySerializer(holiday).data, status=status.HTTP_201_CREATED)


class AdminHolidayDeleteView(AttendanceView):
    permission_map = {"DELETE": "attendance.manage_settings"}
    scope_map = ORG_LEVEL

    def delete(self, request, pk):
        DeleteHoliday(self.repos.holiday).execute(pk)
        return Response(status=status.HTTP_204_NO_CONTENT)


class AdminWorkScheduleDefaultView(AttendanceView):
    permission_map = {"GET": "attendance.manage_settings", "PUT": "attendance.manage_settings"}
    scope_map = ORG_LEVEL

    def get(self, request):
        schedule = self.repos.schedule.get_default()
        return Response(WorkScheduleSerializer(schedule).data if schedule else None)

    def put(self, request):
        serializer = WorkScheduleSetSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        schedule = SetDefaultWorkSchedule(self.repos.schedule).execute(**serializer.validated_data)
        return Response(WorkScheduleSerializer(schedule).data)


class AdminWorkScheduleListView(AttendanceView):
    permission_map = {"GET": "attendance.manage_settings"}
    scope_map = ORG_LEVEL

    def get(self, request):
        rows = [s for s in self.repos.schedule.list_all() if s.date is not None]
        return Response(WorkScheduleSerializer(rows, many=True).data)


class AdminWorkScheduleForDateView(AttendanceView):
    permission_map = {"PUT": "attendance.manage_settings", "DELETE": "attendance.manage_settings"}
    scope_map = ORG_LEVEL

    @staticmethod
    def _parse(date: str) -> datetime.date:
        try:
            return datetime.date.fromisoformat(date)
        except ValueError:
            raise ValidationError("date must be in YYYY-MM-DD format.")

    def put(self, request, date):
        parsed = self._parse(date)
        serializer = WorkScheduleSetSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        schedule = SetWorkScheduleForDate(self.repos.schedule).execute(parsed, **serializer.validated_data)
        return Response(WorkScheduleSerializer(schedule).data)

    def delete(self, request, date):
        DeleteWorkScheduleForDate(self.repos.schedule).execute(self._parse(date))
        return Response(status=status.HTTP_204_NO_CONTENT)


# ------------------------------------------------------------------ managers: everyone's attendance


class AdminAttendanceTodayAllView(AttendanceView):
    """Every active member's status for one day (default today)."""

    permission_map = {"GET": "attendance.report"}
    scope_map = ORG_LEVEL

    def get(self, request):
        date_param = request.query_params.get("date")
        if date_param:
            try:
                target_date = datetime.date.fromisoformat(date_param)
            except ValueError:
                raise ValidationError("date must be in YYYY-MM-DD format.")
        else:
            target_date = datetime.date.today()

        by_user = {a.user_id: a for a in self.repos.attendance.list_for_date(target_date)}
        rows = []
        for user in self.repos.members.list_users():
            attendance = by_user.get(user.id)
            rows.append(
                {
                    "user": UserSerializer(user).data,
                    "attendance": AttendanceSerializer(attendance).data if attendance else None,
                }
            )
        return Response({"date": target_date, "rows": rows})


class AdminAttendanceEditView(AttendanceView):
    permission_map = {"PUT": "attendance.update"}
    scope_map = ORG_LEVEL

    def put(self, request, user_id, date):
        try:
            parsed_date = datetime.date.fromisoformat(date)
        except ValueError:
            raise ValidationError("date must be in YYYY-MM-DD format.")
        if self.repos.members.get_user(user_id) is None:
            raise NotFound()

        serializer = AdminAttendanceEditSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data
        attendance = AdminEditAttendance(self.repos.attendance).execute(
            user_id=user_id, date=parsed_date, check_in=data.get("check_in"),
            check_out=data.get("check_out"), tasks_note=data.get("tasks_note"),
        )
        return Response(AttendanceSerializer(attendance).data)


# ------------------------------------------------------------------ legacy personnel endpoints


def _report_subjects(request, repos):
    """All active members, including managers, can have attendance to report.

    The tenant-bound repository applies the same membership boundary as the
    daily overview and monthly report endpoints.
    """
    return repos.members.list_users()


class AttendanceSubjectsView(AttendanceView):
    """Who the report dropdown offers (v1 replacement for the legacy personnel list)."""

    permission_map = {"GET": "attendance.report"}
    scope_map = ORG_LEVEL

    def get(self, request):
        return Response(UserSerializer(_report_subjects(request, self.repos), many=True).data)


class AdminPersonnelListCreateView(AttendanceView):
    """Legacy client: member list for the report dropdown, and "create personnel".
    Creation delegates to the core member service (account + membership + employee role)."""

    permission_map = {"GET": "attendance.report", "POST": "members.create"}
    scope_map = ORG_LEVEL

    def get(self, request):
        return Response(UserSerializer(_report_subjects(request, self.repos), many=True).data)

    def post(self, request):
        serializer = CreatePersonnelSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data
        employee_role = Role.objects.filter(organization=request.organization, code="employee").first()
        membership = org_services.add_member(
            actor=request.tenant, username=data["personnel_code"], password=data["password"],
            name=data.get("name") or "", personnel_code=data["personnel_code"],
            role_ids=[employee_role.pk] if employee_role else [], request=request,
        )
        return Response(UserSerializer(membership.user).data, status=status.HTTP_201_CREATED)


class AdminPersonnelDeleteView(AttendanceView):
    """Legacy "delete personnel": ends the membership (the account and its history are kept)."""

    permission_map = {"DELETE": "members.delete"}
    scope_map = ORG_LEVEL

    def delete(self, request, pk):
        membership = OrganizationMembership.objects.filter(
            organization=request.organization, user_id=pk
        ).first()
        if membership is None:
            raise NotFound()
        org_services.set_member_status(
            actor=request.tenant, membership=membership,
            status=OrganizationMembership.Status.LEFT, request=request,
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
