"""Leave request use cases: submission, admin approve/reject."""
from __future__ import annotations

import datetime

from domain.exceptions import (
    InvalidLeaveRequestError,
    LeaveAlreadyGrantedError,
    LeaveRequestNotPendingError,
    UserNotFoundError,
)
from domain.repositories import LeaveRequestRepository, MemberRepository


class SubmitLeaveRequest:
    def __init__(self, leave_repo: LeaveRequestRepository):
        self.leave_repo = leave_repo

    def execute(
        self,
        user,
        date: datetime.date,
        type: str,
        category: str,
        start_time: datetime.time | None = None,
        end_time: datetime.time | None = None,
        reason: str | None = None,
    ):
        if type == "hourly":
            if not start_time or not end_time:
                raise InvalidLeaveRequestError("start_time and end_time are required for hourly leave.")
            if end_time <= start_time:
                raise InvalidLeaveRequestError("end_time must be strictly after start_time.")
        else:
            start_time = None
            end_time = None

        if reason and len(reason) > 2000:
            raise InvalidLeaveRequestError("reason must be at most 2000 characters.")

        return self.leave_repo.create(
            user=user,
            date=date,
            type=type,
            category=category,
            start_time=start_time,
            end_time=end_time,
            reason=reason,
            status="pending",
        )


class ReviewLeaveRequest:
    """Approve or reject a pending leave request. Idempotency guard: only
    transitions when the request is currently pending."""

    def __init__(self, leave_repo: LeaveRequestRepository):
        self.leave_repo = leave_repo

    def _execute(self, leave_request, admin, new_status: str, note: str | None = None):
        note = (note or "").strip()
        if len(note) > 1000:
            raise InvalidLeaveRequestError("The note must be at most 1000 characters.")
        if new_status == "rejected" and not note:
            raise InvalidLeaveRequestError("A reason is required to reject a leave request.")
        if leave_request.status != "pending":
            raise LeaveRequestNotPendingError(
                f"Leave request is already {leave_request.status}; cannot transition again."
            )
        leave_request.status = new_status
        leave_request.reviewed_by = admin
        leave_request.reviewed_at = datetime.datetime.now()
        leave_request.review_note = note or None
        self.leave_repo.save(leave_request)
        return leave_request

    def approve(self, leave_request, admin, note: str | None = None):
        return self._execute(leave_request, admin, "approved", note)

    def reject(self, leave_request, admin, note: str | None = None):
        return self._execute(leave_request, admin, "rejected", note)


class GrantDailyLeave:
    """Admin registers an already-approved full-day leave for an employee on a
    given date. If the employee already has a pending daily request for that
    date it is approved instead of creating a duplicate. The target must be an active member
    of the organization the repositories are bound to."""

    def __init__(self, leave_repo: LeaveRequestRepository, member_repo: MemberRepository):
        self.leave_repo = leave_repo
        self.member_repo = member_repo

    def execute(self, user_id: int, date: datetime.date, category: str, admin):
        user = self.member_repo.get_user(user_id)
        if user is None:
            raise UserNotFoundError(f"member {user_id} not found.")

        existing_daily = [
            lv for lv in self.leave_repo.list_for_user_on_date(user_id, date) if lv.type == "daily"
        ]
        if any(lv.status == "approved" for lv in existing_daily):
            raise LeaveAlreadyGrantedError("An approved daily leave already exists for this date.")

        pending = next((lv for lv in existing_daily if lv.status == "pending"), None)
        if pending is not None:
            return ReviewLeaveRequest(self.leave_repo).approve(pending, admin)

        return self.leave_repo.create(
            user=user,
            date=date,
            type="daily",
            category=category,
            start_time=None,
            end_time=None,
            reason=None,
            status="approved",
            reviewed_by=admin,
            reviewed_at=datetime.datetime.now(),
        )
