<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\PtoResource;
use App\Models\Pto;
use App\Services\CapacityService;
use Carbon\Carbon;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class PtoController extends Controller
{
    public function __construct(protected CapacityService $capacityService) {}

    /**
     * List PTO Requests
     *
     * Fetch PTO requests for a team member, optionally constrained to a month.
     *
     * @group Capacity Planning
     *
     * @urlParam team_member_id string required The team member UUID. Example: 550e8400-e29b-41d4-a716-446655440000
     * @urlParam month string nullable The month in YYYY-MM format. Example: 2026-02
     *
     * @response {
     *   "data": [
     *     {
     *       "id": "550e8400-e29b-41d4-a716-446655440001",
     *       "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
     *       "start_date": "2026-02-10",
     *       "end_date": "2026-02-12",
     *       "status": "pending",
     *       "reason": "Family travel"
     *     }
     *   ]
     * }
     */
    public function index(Request $request): JsonResponse
    {
        $data = $request->validate([
            'team_member_id' => 'required|exists:team_members,id',
            'month' => 'nullable|date_format:Y-m',
        ]);

        $query = Pto::with('teamMember')->where('team_member_id', $data['team_member_id']);

        if (! empty($data['month'])) {
            $start = Carbon::createFromFormat('Y-m', $data['month'])->startOfMonth();
            $end = $start->copy()->endOfMonth();

            $query->where(function ($statement) use ($start, $end): void {
                $statement->whereBetween('start_date', [$start, $end])
                    ->orWhereBetween('end_date', [$start, $end])
                    ->orWhere(function ($nested) use ($start, $end): void {
                        $nested->where('start_date', '<=', $start)
                            ->where('end_date', '>=', $end);
                    });
            });
        }

        $ptos = $query->orderBy('start_date')->get();

        return $this->wrapResource(PtoResource::collection($ptos));
    }

    /**
     * Request PTO
     *
     * Create a PTO request for a team member and approve it immediately.
     *
     * @group Capacity Planning
     *
     * @bodyParam team_member_id string required The team member UUID. Example: 550e8400-e29b-41d4-a716-446655440000
     * @bodyParam start_date string required The first day of the PTO. Example: 2026-02-10
     * @bodyParam end_date string required The final day of the PTO. Example: 2026-02-12
     * @bodyParam reason string nullable Optional reason for the PTO.
     *
     * @response 201 {
     *   "data": {
     *     "id": "550e8400-e29b-41d4-a716-446655440001",
     *     "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
     *     "start_date": "2026-02-10",
     *     "end_date": "2026-02-12",
     *     "status": "approved",
     *     "reason": "Family travel"
     *   }
     * }
     */
    public function store(Request $request): JsonResponse
    {
        $data = $request->validate([
            'team_member_id' => 'required|exists:team_members,id',
            'start_date' => 'required|date',
            'end_date' => 'required|date|after_or_equal:start_date',
            'reason' => 'nullable|string',
        ]);

        try {
            $pto = Pto::create(array_merge($data, ['status' => 'approved']));
            $months = $this->monthsBetween($pto->start_date, $pto->end_date);
            $this->capacityService->forgetCapacityCacheForTeamMember($pto->team_member_id, $months);

            foreach ($months as $month) {
                $this->capacityService->forgetCapacityCacheForMonth($month);
            }

            $pto->load('teamMember');

            return $this->wrapResource(new PtoResource($pto), 201);
        } catch (UniqueConstraintViolationException $e) {
            return response()->json([
                'message' => 'A PTO request with these details already exists.',
                'errors' => [
                    'general' => ['A PTO request with these details already exists.'],
                ],
            ], 422);
        }
    }

    /**
     * Approve PTO
     *
     * Approve a pending PTO request and refresh the affected capacity caches.
     *
     * @group Capacity Planning
     *
     * @urlParam id string required The PTO UUID that needs approval. Example: 550e8400-e29b-41d4-a716-446655440001
     *
     * @response {
     *   "data": {
     *     "id": "550e8400-e29b-41d4-a716-446655440001",
     *     "status": "approved"
     *   }
     * }
     */
    public function approve(string $id): JsonResponse
    {
        $pto = Pto::with('teamMember')->findOrFail($id);

        if ($pto->status !== 'approved') {
            $pto->status = 'approved';
            $pto->save();
            $months = $this->monthsBetween($pto->start_date, $pto->end_date);
            $this->capacityService->forgetCapacityCacheForTeamMember($pto->team_member_id, $months);

            foreach ($months as $month) {
                $this->capacityService->forgetCapacityCacheForMonth($month);
            }
        }

        $pto->load('teamMember');

        return $this->wrapResource(new PtoResource($pto));
    }

    public function destroy(string $id): JsonResponse
    {
        $pto = Pto::find($id);

        if (! $pto) {
            return response()->json(['message' => 'PTO not found'], 404);
        }

        $months = $this->monthsBetween($pto->start_date, $pto->end_date);
        $teamMemberId = $pto->team_member_id;
        $pto->delete();

        $this->capacityService->forgetCapacityCacheForTeamMember($teamMemberId, $months);

        foreach ($months as $month) {
            $this->capacityService->forgetCapacityCacheForMonth($month);
        }

        return response()->json(['message' => 'PTO deleted']);
    }

    private function monthsBetween(Carbon|string $start, Carbon|string $end): array
    {
        $startMonth = Carbon::create($start)->copy()->startOfMonth();
        $endMonth = Carbon::create($end)->copy()->startOfMonth();
        $months = [];

        while ($startMonth <= $endMonth) {
            $months[] = $startMonth->format('Y-m');
            $startMonth->addMonth();
        }

        return $months;
    }
}
