<?php

namespace Modules\Leave\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Leave;
use App\Models\LeaveType;
use App\Models\User;
use App\Notifications\LeaveCancelledNotification;
use App\Notifications\LeaveRequestSubmittedNotification;
use App\Notifications\LeaveReviewedNotification;
use App\Notifications\LeaveSubmittedNotification;
use App\Notifications\LeaveWithdrawalRequestedNotification;
use App\Services\HrmsNotificationService;
use App\Services\Leave\LeaveLedgerService;
use App\Services\Leave\LeaveRuleValidator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class LeaveController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        $this->authorize('viewAny', Leave::class);

        $query = Leave::with(['user', 'leaveType']);
        if ($request->user()->company_id) {
            $query->where('company_id', $request->user()->company_id);
        }

        $visibleIds = $request->user()->visibleStaffUserIds();
        $query->whereIn('user_id', $visibleIds);

        $leaves = $query->orderByDesc('created_at')->paginate($request->input('per_page', 15));

        return response()->json($leaves);
    }

    public function types(Request $request): JsonResponse
    {
        $this->authorize('create', Leave::class);

        $companyId = $request->user()->company_id;
        $types = LeaveType::query()
            ->when($companyId, fn ($q) => $q->where('company_id', $companyId))
            ->orderBy('name')
            ->get(['id', 'name', 'short_code', 'is_paid', 'is_unlimited', 'total_leaves']);

        return response()->json(['data' => $types]);
    }

    public function store(Request $request, LeaveRuleValidator $ruleValidator, LeaveLedgerService $ledger): JsonResponse
    {
        $this->authorize('create', Leave::class);

        $companyId = $request->user()->company_id;
        $validated = $request->validate([
            'leave_type_id' => [
                'required',
                Rule::exists('leave_types', 'id')->where(function ($q) use ($companyId) {
                    if ($companyId) {
                        $q->where('company_id', $companyId);
                    }
                }),
            ],
            'start_date' => 'required|date',
            'end_date' => 'required|date|after_or_equal:start_date',
            'reason' => 'required|string',
            'is_half_day' => 'boolean',
            'half_leave' => ['nullable', Rule::in(['before_break', 'after_break', 'first_half', 'second_half'])],
            'attachment' => ['nullable', 'file', 'max:10240', 'mimes:pdf,jpg,jpeg,png,doc,docx'],
        ]);

        $user = User::findOrFail($request->user()->id);
        $leaveType = LeaveType::query()
            ->when($companyId, fn ($q) => $q->where('company_id', $companyId))
            ->findOrFail($validated['leave_type_id']);

        $startDate = \Carbon\Carbon::parse($validated['start_date']);
        $endDate = \Carbon\Carbon::parse($validated['end_date']);
        $isHalfDay = (bool) ($validated['is_half_day'] ?? false);
        if ($isHalfDay && ! $startDate->isSameDay($endDate)) {
            throw ValidationException::withMessages([
                'is_half_day' => 'Half-day leave is allowed only for single-day leave requests.',
            ]);
        }
        $calc = $ruleValidator->validateForCreate($request, $user, $leaveType, $startDate, $endDate, $isHalfDay);

        $leave = Leave::create([
            'company_id' => $companyId,
            'user_id' => $request->user()->id,
            'leave_type_id' => $validated['leave_type_id'],
            'start_date' => $validated['start_date'],
            'end_date' => $validated['end_date'],
            'total_days' => $calc['chargeable_days'],
            'is_half_day' => $isHalfDay,
            'half_leave' => $validated['half_leave'] ?? 'before_break',
            'reason' => $validated['reason'],
            'is_paid' => $request->boolean('is_paid', (bool) $leaveType->is_paid),
            'status' => 'pending',
        ]);

        try {
            $ledger->holdPending($leave);
        } catch (\InvalidArgumentException $e) {
            $leave->delete();

            throw ValidationException::withMessages([
                'start_date' => $e->getMessage(),
            ]);
        }

        $leave->load(['user', 'leaveType']);
        app(HrmsNotificationService::class)->notifyLeaveApprovers(
            $user,
            new LeaveSubmittedNotification($leave)
        );
        $user->notify(new LeaveRequestSubmittedNotification($leave));

        return response()->json($leave->load('leaveType'), 201);
    }

    public function show(Leave $leave): JsonResponse
    {
        $this->authorize('view', $leave);

        return response()->json($leave->load(['user', 'leaveType']));
    }

    public function update(Request $request, Leave $leave, LeaveRuleValidator $ruleValidator): JsonResponse
    {
        $this->authorize('update', $leave);

        if ($leave->status !== 'pending') {
            return response()->json(['message' => 'Only pending leaves can be updated.'], 422);
        }

        $validated = $request->validate([
            'start_date' => 'sometimes|date',
            'end_date' => 'sometimes|date',
            'reason' => 'sometimes|string',
        ]);

        if (array_key_exists('start_date', $validated) || array_key_exists('end_date', $validated)) {
            $startDate = \Carbon\Carbon::parse($validated['start_date'] ?? $leave->start_date);
            $endDate = \Carbon\Carbon::parse($validated['end_date'] ?? $leave->end_date);
            if ($endDate->lt($startDate)) {
                throw ValidationException::withMessages([
                    'end_date' => 'The end date must be on or after the start date.',
                ]);
            }
            $user = $leave->user ?? User::findOrFail($leave->user_id);
            $ruleValidator->assertNoOverlappingLeave($user, $startDate, $endDate, $leave->id);
        }

        $leave->update($validated);

        return response()->json($leave->fresh());
    }

    public function destroy(Leave $leave, LeaveLedgerService $ledger): JsonResponse
    {
        $this->authorize('delete', $leave);

        if ($leave->status !== 'pending') {
            return response()->json(['message' => 'Only pending leaves can be deleted.'], 422);
        }

        $leave->load(['user', 'leaveType']);
        $user = $leave->user;
        if ($user) {
            app(HrmsNotificationService::class)->notifyLeaveApprovers(
                $user,
                new LeaveCancelledNotification($leave)
            );
        }

        try {
            $ledger->releasePending($leave);
        } catch (\Throwable) {
            // Leave may not have a ledger hold; still allow cancel.
        }

        $leave->delete();

        return response()->json(null, 204);
    }

    public function withdraw(Request $request, Leave $leave, LeaveLedgerService $ledger): JsonResponse
    {
        $this->authorize('withdraw', $leave);

        $validated = $request->validate([
            'withdrawal_reason' => ['required', 'string', 'min:3', 'max:1000'],
        ]);

        $leave->load(['user', 'leaveType']);

        $leave->update([
            'status' => 'withdrawal_pending',
            'withdrawal_reason' => $validated['withdrawal_reason'],
        ]);

        $user = $leave->user;
        if ($user) {
            app(HrmsNotificationService::class)->notifyLeaveApprovers(
                $user,
                new LeaveWithdrawalRequestedNotification($leave->fresh(['user', 'leaveType']))
            );
        }

        return response()->json($leave->fresh()->load('leaveType'));
    }

    public function reviewWithdraw(Request $request, Leave $leave, LeaveLedgerService $ledger): JsonResponse
    {
        $reviewer = $request->user();
        if ($reviewer->company_id && (int) $leave->company_id !== (int) $reviewer->company_id) {
            abort(403);
        }

        $this->authorize('approve', $leave);

        if ($leave->status !== 'withdrawal_pending') {
            return response()->json(['message' => 'Only withdrawal requests can be reviewed.'], 422);
        }

        $validated = $request->validate([
            'decision' => ['required', Rule::in(['accepted', 'rejected'])],
            'remark' => ['nullable', 'string', 'max:1000'],
        ]);

        $leave->load(['user', 'leaveType']);
        $decision = $validated['decision'];
        $remark = trim((string) ($validated['remark'] ?? ''));

        if ($decision === 'accepted') {
            $ledger->reverseApproved($leave);
            $leave->update([
                'status' => 'cancelled',
                'remark' => $remark !== '' ? $remark : $leave->remark,
            ]);
            $outcome = 'withdrawal accepted';
        } else {
            $leave->update([
                'status' => 'approved',
                'withdrawal_reason' => null,
                'remark' => $remark !== '' ? $remark : $leave->remark,
            ]);
            $outcome = 'withdrawal rejected';
        }

        $leave->load(['user', 'leaveType']);
        $leave->user?->notify(new LeaveReviewedNotification($leave, $outcome));

        return response()->json($leave->fresh()->load('leaveType'));
    }

    public function updateStatus(Request $request, Leave $leave, LeaveLedgerService $ledger): JsonResponse
    {
        $reviewer = $request->user();
        if ($reviewer->company_id && (int) $leave->company_id !== (int) $reviewer->company_id) {
            abort(403);
        }

        $this->authorize('approve', $leave);

        $validated = $request->validate([
            'status' => ['required', Rule::in(['approved', 'rejected'])],
            'remark' => ['nullable', 'string', 'max:1000'],
        ]);

        $previousStatus = $leave->status;

        $leave->update([
            'status' => $validated['status'],
            'remark' => trim((string) ($validated['remark'] ?? '')) !== '' ? $validated['remark'] : null,
            'is_paid' => $leave->is_paid || (bool) ($leave->leaveType?->is_paid ?? false),
        ]);

        if ($previousStatus === 'pending' && $validated['status'] === 'approved') {
            $ledger->consumePending($leave);
        } elseif ($previousStatus === 'pending' && $validated['status'] === 'rejected') {
            $ledger->releasePending($leave);
        } elseif ($previousStatus === 'approved' && $validated['status'] === 'rejected') {
            $ledger->reverseApproved($leave);
        }

        $leave->load(['user', 'leaveType']);
        $leave->user?->notify(new LeaveReviewedNotification($leave, $validated['status']));

        return response()->json($leave);
    }
}
