<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\ProjectResource;
use App\Http\Resources\ProjectStatusResource;
use App\Http\Resources\ProjectTypeResource;
use App\Models\Project;
use App\Models\ProjectStatus;
use App\Models\ProjectType;
use App\Services\ProjectService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

/**
 * @group Projects
 *
 * Endpoints for managing projects.
 */
class ProjectController extends Controller
{
    /**
     * Project Service instance
     */
    protected ProjectService $projectService;

    /**
     * Constructor
     */
    public function __construct(ProjectService $projectService)
    {
        $this->projectService = $projectService;
    }

    /**
     * List all projects
     *
     * Get a list of all projects with optional filtering by status and type.
     *
     * @authenticated
     *
     * @queryParam status_id integer Filter by status ID. Example: 1
     * @queryParam type_id integer Filter by type ID. Example: 2
     *
     * @response 200 {
     *   "data": [
     *     {
     *       "id": "550e8400-e29b-41d4-a716-446655440000",
     *       "code": "PROJ-001",
     *       "title": "Client Dashboard Redesign",
     *       "status": {"id": 1, "name": "Pre-sales"},
     *       "type": {"id": 2, "name": "Support"},
     *       "approved_estimate": "120.00",
     *       "forecasted_effort": {"2024-02": 40, "2024-03": 60, "2024-04": 20},
     *       "created_at": "2024-01-15T10:00:00.000000Z",
     *       "updated_at": "2024-01-15T10:00:00.000000Z"
     *     }
     *   ]
     * }
     */
    public function index(Request $request): JsonResponse
    {
        $statusId = $request->query('status_id') ? (int) $request->query('status_id') : null;
        $typeId = $request->query('type_id') ? (int) $request->query('type_id') : null;

        $projects = $this->projectService->getAll($statusId, $typeId);

        return $this->wrapResource(ProjectResource::collection($projects));
    }

    /**
     * Create a new project
     *
     * Create a new project with code, title, and type.
     *
     * @authenticated
     *
     * @bodyParam code string required Project code (must be unique). Example: PROJ-001
     * @bodyParam title string required Project title. Example: Client Dashboard Redesign
     * @bodyParam type_id integer required Project type ID. Example: 1
     *
     * @response 201 {
     *   "data": {
     *     "id": "550e8400-e29b-41d4-a716-446655440000",
     *     "code": "PROJ-001",
     *     "title": "Client Dashboard Redesign",
     *     "status": {"id": 1, "name": "Pre-sales"},
     *     "type": {"id": 1, "name": "Project"}
     *   }
     * }
     * @response 422 {"message":"Validation failed","errors":{"code":["Project code must be unique"],"title":["The title field is required."]}}
     */
    public function store(Request $request): JsonResponse
    {
        try {
            $project = $this->projectService->create($request->all());

            return $this->wrapResource(new ProjectResource($project), 201);
        } catch (ValidationException $e) {
            return response()->json([
                'message' => 'Validation failed',
                'errors' => $e->validator->errors(),
            ], 422);
        }
    }

    /**
     * Get a single project
     *
     * Get details of a specific project by ID.
     *
     * @authenticated
     *
     * @urlParam id string required Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000
     *
     * @response 200 {
     *   "data": {
     *     "id": "550e8400-e29b-41d4-a716-446655440000",
     *     "code": "PROJ-001",
     *     "title": "Client Dashboard Redesign",
     *     "status": {"id": 1, "name": "Pre-sales"},
     *     "type": {"id": 1, "name": "Project"},
     *     "approved_estimate": "120.00",
     *     "forecasted_effort": {"2024-02": 40, "2024-03": 60}
     *   }
     * }
     * @response 404 {"message":"Project not found"}
     */
    public function show(string $id): JsonResponse
    {
        $project = $this->projectService->findById($id);

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

        return $this->wrapResource(new ProjectResource($project));
    }

    /**
     * Update a project
     *
     * Update details of an existing project.
     *
     * @authenticated
     *
     * @urlParam id string required Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000
     *
     * @bodyParam code string Project code (must be unique). Example: PROJ-002
     * @bodyParam title string Project title. Example: Updated Title
     * @bodyParam type_id integer Project type ID. Example: 2
     *
     * @response 200 {
     *   "data": {
     *     "id": "550e8400-e29b-41d4-a716-446655440000",
     *     "code": "PROJ-002",
     *     "title": "Updated Title",
     *     "type": {"id": 2, "name": "Support"}
     *   }
     * }
     * @response 404 {"message":"Project not found"}
     * @response 422 {"message":"Validation failed","errors":{"type_id":["The selected type id is invalid."]}}
     */
    public function update(Request $request, string $id): JsonResponse
    {
        $project = Project::find($id);

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

        try {
            $project = $this->projectService->update($project, $request->only([
                'code', 'title', 'type_id', 'status_id', 'approved_estimate',
            ]));

            return $this->wrapResource(new ProjectResource($project));
        } catch (ValidationException $e) {
            return response()->json([
                'message' => 'Validation failed',
                'errors' => $e->validator->errors(),
            ], 422);
        }
    }

    /**
     * Transition project status
     *
     * Transition project to a new status following the state machine rules.
     *
     * @authenticated
     *
     * @urlParam id string required Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000
     *
     * @bodyParam status_id integer required Target status ID. Example: 2
     *
     * @response 200 {
     *   "data": {
     *     "id": "550e8400-e29b-41d4-a716-446655440000",
     *     "status": {"id": 2, "name": "SOW Approval"}
     *   }
     * }
     * @response 404 {"message":"Project not found"}
     * @response 422 {"message":"Cannot transition from Pre-sales to Done"}
     */
    public function updateStatus(Request $request, string $id): JsonResponse
    {
        $project = Project::with('status')->find($id);

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

        $request->validate([
            'status_id' => 'required|integer|exists:project_statuses,id',
        ]);

        try {
            $project = $this->projectService->transitionStatus(
                $project,
                (int) $request->input('status_id')
            );

            return $this->wrapResource(new ProjectResource($project));
        } catch (\RuntimeException $e) {
            return response()->json([
                'message' => $e->getMessage(),
            ], 422);
        }
    }

    /**
     * Set approved estimate
     *
     * Set the approved billable hours estimate for a project.
     *
     * @authenticated
     *
     * @urlParam id string required Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000
     *
     * @bodyParam approved_estimate number required Approved estimate hours (must be > 0). Example: 120
     *
     * @response 200 {
     *   "data": {
     *     "id": "550e8400-e29b-41d4-a716-446655440000",
     *     "approved_estimate": "120.00"
     *   }
     * }
     * @response 404 {"message":"Project not found"}
     * @response 422 {"message":"Approved estimate must be greater than 0"}
     */
    public function setEstimate(Request $request, string $id): JsonResponse
    {
        $project = Project::find($id);

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

        $request->validate([
            'approved_estimate' => 'required|numeric',
        ]);

        try {
            $project = $this->projectService->setApprovedEstimate(
                $project,
                (float) $request->input('approved_estimate')
            );

            return $this->wrapResource(new ProjectResource($project));
        } catch (\RuntimeException $e) {
            return response()->json([
                'message' => $e->getMessage(),
            ], 422);
        }
    }

    /**
     * Set forecasted effort
     *
     * Set the month-by-month forecasted effort breakdown.
     *
     * @authenticated
     *
     * @urlParam id string required Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000
     *
     * @bodyParam forecasted_effort object required Monthly effort breakdown. Example: {"2024-02": 40, "2024-03": 60}
     *
     * @response 200 {
     *   "data": {
     *     "id": "550e8400-e29b-41d4-a716-446655440000",
     *     "forecasted_effort": {"2024-02": 40, "2024-03": 60}
     *   }
     * }
     * @response 404 {"message":"Project not found"}
     * @response 422 {"message":"Forecasted effort exceeds approved estimate by more than 5%"}
     */
    public function setForecast(Request $request, string $id): JsonResponse
    {
        $project = Project::find($id);

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

        $request->validate([
            'forecasted_effort' => 'required|array',
        ]);

        try {
            $project = $this->projectService->setForecastedEffort(
                $project,
                $request->input('forecasted_effort')
            );

            return $this->wrapResource(new ProjectResource($project));
        } catch (\RuntimeException $e) {
            return response()->json([
                'message' => $e->getMessage(),
            ], 422);
        }
    }

    /**
     * Get all project types
     *
     * @authenticated
     *
     * @response 200 {
     *   "data": [
     *     {"id": 1, "name": "Project"},
     *     {"id": 2, "name": "Support"},
     *     {"id": 3, "name": "Engagement"}
     *   ]
     * }
     */
    public function types(): JsonResponse
    {
        $types = ProjectType::orderBy('name')->get(['id', 'name']);

        return $this->wrapResource(ProjectTypeResource::collection($types));
    }

    /**
     * Get all project statuses
     *
     * @authenticated
     *
     * @response 200 {
     *   "data": [
     *     {"id": 1, "name": "Pre-sales", "order": 1},
     *     {"id": 2, "name": "SOW Approval", "order": 2},
     *     {"id": 3, "name": "Gathering Estimates", "order": 3}
     *   ]
     * }
     */
    public function statuses(): JsonResponse
    {
        $statuses = ProjectStatus::orderBy('order')->get(['id', 'name', 'order']);

        return $this->wrapResource(ProjectStatusResource::collection($statuses));
    }

    /**
     * Delete a project
     *
     * Delete a project. Cannot delete if project has allocations or actuals.
     *
     * @authenticated
     *
     * @urlParam id string required Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000
     *
     * @response 200 {"message":"Project deleted successfully"}
     * @response 404 {"message":"Project not found"}
     * @response 422 {"message":"Cannot delete project with allocations"}
     */
    public function destroy(string $id): JsonResponse
    {
        $project = Project::find($id);

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

        $canDelete = $this->projectService->canDelete($project);

        if (! $canDelete['canDelete']) {
            return response()->json([
                'message' => "Cannot delete project with {$canDelete['reason']}",
            ], 422);
        }

        $project->delete();

        return response()->json([
            'message' => 'Project deleted successfully',
        ]);
    }
}
