"""Django ORM-backed implementations of the domain repository interfaces.

Tenant-owned repositories are bound to one organization at construction time. Every query is
filtered by it and every create stamps it, so a use case can never read or write another
organization's rows.
"""
from __future__ import annotations

import datetime
from typing import Optional

from apps.organizations.models import Organization, OrganizationMembership
from domain.repositories import (
    AttendanceRepository,
    HolidayRepository,
    LeaveRequestRepository,
    MemberRepository,
    UserRepository,
    WorkScheduleRepository,
)
from infrastructure.models import Attendance, Holiday, LeaveRequest, User, WorkSchedule


class DjangoUserRepository(UserRepository):
    def save(self, user) -> None:
        user.save()


class _OrganizationBound:
    def __init__(self, organization: Organization):
        if organization is None or organization.pk is None:
            raise ValueError("A tenant repository requires a saved organization.")
        self.organization = organization


class DjangoMemberRepository(_OrganizationBound, MemberRepository):
    def _active(self):
        return User.objects.filter(
            memberships__organization=self.organization,
            memberships__status=OrganizationMembership.Status.ACTIVE,
        )

    def get_user(self, user_id: int):
        return self._active().filter(pk=user_id).first()

    def list_users(self):
        return list(self._active().order_by("personnel_code"))


class DjangoAttendanceRepository(_OrganizationBound, AttendanceRepository):
    def _qs(self):
        return Attendance.objects.filter(organization=self.organization)

    def get_for_day(self, user_id: int, date: datetime.date):
        return self._qs().filter(user_id=user_id, date=date).first()

    def create(self, user_id: int, date: datetime.date, **fields):
        return Attendance.objects.create(organization=self.organization, user_id=user_id, date=date, **fields)

    def save(self, attendance) -> None:
        if attendance.organization_id != self.organization.pk:
            raise ValueError("Attendance belongs to another organization.")
        attendance.save()

    def list_for_range(self, user_id: int, start: datetime.date, end: datetime.date):
        return list(self._qs().filter(user_id=user_id, date__gte=start, date__lte=end).order_by("date"))

    def get_or_create_for_day(self, user_id: int, date: datetime.date):
        obj, _created = Attendance.objects.get_or_create(
            organization=self.organization, user_id=user_id, date=date
        )
        return obj

    def find_pending_default_checkout(self, before: datetime.date):
        return list(self._qs().filter(check_in__isnull=False, check_out__isnull=True, date__lt=before))

    def list_for_date(self, date: datetime.date):
        return list(self._qs().filter(date=date).select_related("user").order_by("user__personnel_code"))


class DjangoHolidayRepository(_OrganizationBound, HolidayRepository):
    def _qs(self):
        return Holiday.objects.filter(organization=self.organization)

    def list_for_range(self, start: datetime.date, end: datetime.date):
        return list(self._qs().filter(date__gte=start, date__lte=end).order_by("date"))

    def list_all(self):
        return list(self._qs().order_by("date"))

    def exists_on_date(self, date: datetime.date) -> bool:
        return self._qs().filter(date=date).exists()

    def create(self, date: datetime.date, title: str, description: str | None):
        return Holiday.objects.create(
            organization=self.organization, date=date, title=title, description=description
        )

    def delete(self, holiday_id: int) -> None:
        self._qs().filter(id=holiday_id).delete()


class DjangoWorkScheduleRepository(_OrganizationBound, WorkScheduleRepository):
    def _qs(self):
        return WorkSchedule.objects.filter(organization=self.organization)

    def get_for_date(self, date: datetime.date):
        return self._qs().filter(date=date).first()

    def get_default(self):
        return self._qs().filter(date__isnull=True).first()

    def get_effective_for_date(self, date: datetime.date):
        return self.get_for_date(date) or self.get_default()

    def list_all(self):
        return list(self._qs().order_by("date"))

    def _upsert(self, date, standard_check_in, standard_check_out):
        existing = self._qs().filter(date=date).first()
        if existing is None:
            return WorkSchedule.objects.create(
                organization=self.organization, date=date,
                standard_check_in=standard_check_in, standard_check_out=standard_check_out,
            )
        existing.standard_check_in = standard_check_in
        existing.standard_check_out = standard_check_out
        existing.save()
        return existing

    def set_default(self, standard_check_in, standard_check_out):
        return self._upsert(None, standard_check_in, standard_check_out)

    def set_for_date(self, date: datetime.date, standard_check_in, standard_check_out):
        return self._upsert(date, standard_check_in, standard_check_out)

    def delete_for_date(self, date: datetime.date) -> None:
        self._qs().filter(date=date).delete()


class DjangoLeaveRequestRepository(_OrganizationBound, LeaveRequestRepository):
    def _qs(self):
        return LeaveRequest.objects.filter(organization=self.organization)

    def create(self, **fields):
        return LeaveRequest.objects.create(organization=self.organization, **fields)

    def get_by_id(self, leave_id: int):
        return self._qs().filter(id=leave_id).select_related("user").first()

    def list_for_user(self, user_id: int):
        return list(self._qs().filter(user_id=user_id).select_related("user").order_by("-date"))

    def list_for_user_on_date(self, user_id: int, date: datetime.date):
        return list(self._qs().filter(user_id=user_id, date=date))

    def list_all(self, status: Optional[str] = None):
        qs = self._qs().select_related("user", "reviewed_by").order_by("-date")
        if status == "reviewed":  # the history: everything that was approved or rejected
            qs = qs.exclude(status="pending").order_by("-reviewed_at", "-id")
        elif status:
            qs = qs.filter(status=status)
        return list(qs)

    def list_approved_for_user_range(self, user_id: int, start: datetime.date, end: datetime.date):
        return list(
            self._qs().filter(
                user_id=user_id, date__gte=start, date__lte=end, status=LeaveRequest.Status.APPROVED
            ).order_by("date")
        )

    def save(self, leave_request) -> None:
        if leave_request.organization_id != self.organization.pk:
            raise ValueError("Leave request belongs to another organization.")
        leave_request.save()
