<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Allocation;
use App\Models\Project;
use App\Models\ProjectMonthPlan;
use App\Models\TeamMember;
use App\Services\ReconciliationCalculator;
use App\Services\VarianceCalculator;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

/**
 * @group Reports
 *
 * Endpoints for generating management reports with did/is/will views.
 */
class ReportController extends Controller
{
    public function __construct(
        protected ReconciliationCalculator $reconciliationCalculator,
        protected VarianceCalculator $varianceCalculator
    ) {
    }

    /**
     * Get allocation report
     *
     * Returns aggregated allocation data with lifecycle totals, month plans,
     * execution hours, and variances. View type (did/is/will) is inferred
     * from the date range relative to current month.
     *
     * @authenticated
     *
     * @queryParam start_date string required Start date (YYYY-MM-DD). Example: 2026-01-01
     * @queryParam end_date string required End date (YYYY-MM-DD). Example: 2026-03-31
     * @queryParam project_ids array optional Filter by project IDs. Example: ["uuid1", "uuid2"]
     * @queryParam member_ids array optional Filter by team member IDs. Example: ["uuid1", "uuid2"]
     *
     * @response 200 {
     *   "period": { "start": "2026-01-01", "end": "2026-03-31" },
     *   "view_type": "is",
     *   "projects": [...],
     *   "members": [...],
     *   "aggregates": {
     *     "total_planned": 7200,
     *     "total_allocated": 7100,
     *     "total_variance": -100,
     *     "status": "MATCH"
     *   }
     * }
     */
    public function allocations(Request $request): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'start_date' => 'required|date_format:Y-m-d',
            'end_date' => 'required|date_format:Y-m-d|after_or_equal:start_date',
            'project_ids' => 'nullable|array',
            'project_ids.*' => 'uuid|exists:projects,id',
            'member_ids' => 'nullable|array',
            'member_ids.*' => 'uuid|exists:team_members,id',
        ]);

        if ($validator->fails()) {
            return response()->json([
                'message' => 'Validation failed',
                'errors' => $validator->errors(),
            ], 422);
        }

        $startDate = Carbon::parse($request->input('start_date'));
        $endDate = Carbon::parse($request->input('end_date'));
        $viewType = $this->determineViewType($startDate, $endDate);

        // Get projects with optional filtering
        $projectsQuery = Project::query();
        if ($request->has('project_ids')) {
            $projectsQuery->whereIn('id', $request->input('project_ids'));
        }
        $projects = $projectsQuery->get();

        // Get team members with optional filtering
        $membersQuery = TeamMember::query();
        if ($request->has('member_ids')) {
            $membersQuery->whereIn('id', $request->input('member_ids'));
        }
        $members = $membersQuery->get();

        // Get all plans for the period
        $plans = ProjectMonthPlan::whereBetween('month', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
            ->when($request->has('project_ids'), fn ($q) => $q->whereIn('project_id', $request->input('project_ids')))
            ->get()
            ->groupBy('project_id');

        // Get all allocations for the period
        $allocations = Allocation::whereBetween('month', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
            ->when($request->has('project_ids'), fn ($q) => $q->whereIn('project_id', $request->input('project_ids')))
            ->when($request->has('member_ids'), fn ($q) => $q->whereIn('team_member_id', $request->input('member_ids')))
            ->get();

        // Build project report data
        $projectData = $this->buildProjectData($projects, $plans, $allocations, $startDate, $endDate);

        // Build member report data
        $memberData = $this->buildMemberData($members, $allocations, $startDate, $endDate);

        // Calculate aggregates
        $aggregates = $this->calculateAggregates($projectData);

        return response()->json([
            'period' => [
                'start' => $startDate->format('Y-m-d'),
                'end' => $endDate->format('Y-m-d'),
            ],
            'view_type' => $viewType,
            'projects' => $projectData,
            'members' => $memberData,
            'aggregates' => $aggregates,
        ]);
    }

    /**
     * Determine view type based on date range relative to current month.
     */
    private function determineViewType(Carbon $startDate, Carbon $endDate): string
    {
        $now = Carbon::now();
        $currentMonthStart = $now->copy()->startOfMonth();
        $currentMonthEnd = $now->copy()->endOfMonth();

        $rangeStart = $startDate->copy()->startOfMonth();
        $rangeEnd = $endDate->copy()->endOfMonth();

        // All dates are in the past -> 'did'
        if ($rangeEnd->lt($currentMonthStart)) {
            return 'did';
        }

        // All dates are in the future -> 'will'
        if ($rangeStart->gt($currentMonthEnd)) {
            return 'will';
        }

        // Includes current month -> 'is'
        return 'is';
    }

    /**
     * Build project report data with lifecycle totals and period execution.
     *
     * @return array<int, array{
     *   id: string,
     *   code: string,
     *   title: string,
     *   approved_estimate: float,
     *   lifecycle_status: string,
     *   plan_sum: float,
     *   period_planned: float,
     *   period_allocated: float,
     *   period_variance: float,
     *   period_status: string,
     *   months: array
     * }>
     */
    private function buildProjectData($projects, $plans, $allocations, Carbon $startDate, Carbon $endDate): array
    {
        $projectData = [];

        foreach ($projects as $project) {
            $projectPlans = $plans->get($project->id, collect());

            // Calculate lifecycle reconciliation (all plans for this project)
            $allProjectPlans = ProjectMonthPlan::where('project_id', $project->id)->get();
            $lifecycleStatus = $this->reconciliationCalculator->calculateStatus($project, $allProjectPlans);
            $planSum = $this->reconciliationCalculator->calculatePlanSum($project, $allProjectPlans);

            // Calculate period metrics (only within date range)
            $periodPlans = $projectPlans->filter(fn ($p) =>
                Carbon::parse($p->month)->between($startDate, $endDate)
            );

            $periodPlanned = $periodPlans->sum('planned_hours');

            // Get allocations for this project in the period
            $projectAllocations = $allocations->where('project_id', $project->id);
            $periodAllocated = $projectAllocations->sum('allocated_hours');

            $periodVariance = $periodAllocated - $periodPlanned;
            $periodStatus = $this->varianceCalculator->determineStatus($periodVariance);

            // Build monthly breakdown
            $months = $this->buildProjectMonthBreakdown($projectPlans, $projectAllocations, $startDate, $endDate);

            $projectData[] = [
                'id' => $project->id,
                'code' => $project->code,
                'title' => $project->title,
                'approved_estimate' => (float) $project->approved_estimate,
                'lifecycle_status' => $lifecycleStatus,
                'plan_sum' => $planSum,
                'period_planned' => $periodPlanned,
                'period_allocated' => $periodAllocated,
                'period_variance' => $periodVariance,
                'period_status' => $periodStatus,
                'months' => $months,
            ];
        }

        return $projectData;
    }

    /**
     * Build monthly breakdown for a project.
     *
     * @return array<int, array{
     *   month: string,
     *   planned_hours: float|null,
     *   is_blank: bool,
     *   allocated_hours: float,
     *   variance: float,
     *   status: string
     * }>
     */
    private function buildProjectMonthBreakdown($projectPlans, $projectAllocations, Carbon $startDate, Carbon $endDate): array
    {
        $months = [];
        $current = $startDate->copy()->startOfMonth();

        while ($current->lte($endDate)) {
            $monthKey = $current->format('Y-m');

            // Get plan for this month
            $plan = $projectPlans->first(fn ($p) =>
                Carbon::parse($p->month)->format('Y-m') === $monthKey
            );

            $plannedHours = $plan?->planned_hours;
            $isBlank = $plannedHours === null;

            // Get allocations for this month
            $monthAllocations = $projectAllocations->filter(fn ($a) =>
                Carbon::parse($a->month)->format('Y-m') === $monthKey
            );

            $allocatedHours = $monthAllocations->sum('allocated_hours');
            $variance = $allocatedHours - ($plannedHours ?? 0);
            $status = $this->varianceCalculator->determineStatus($variance);

            $months[] = [
                'month' => $monthKey,
                'planned_hours' => $isBlank ? null : (float) $plannedHours,
                'is_blank' => $isBlank,
                'allocated_hours' => $allocatedHours,
                'variance' => $variance,
                'status' => $status,
            ];

            $current->addMonth();
        }

        return $months;
    }

    /**
     * Build member report data with capacity and utilization.
     *
     * @return array<int, array{
     *   id: string,
     *   name: string,
     *   period_allocated: float,
     *   period_untracked: float,
     *   total_hours: float,
     *   projects: array
     * }>
     */
    private function buildMemberData($members, $allocations, Carbon $startDate, Carbon $endDate): array
    {
        $memberData = [];

        foreach ($members as $member) {
            // Get allocations for this member in the period (excluding untracked)
            $memberAllocations = $allocations->filter(fn ($a) =>
                $a->team_member_id === $member->id
            );

            $periodAllocated = $memberAllocations->sum('allocated_hours');

            // Group by project
            $projects = $memberAllocations
                ->groupBy('project_id')
                ->map(fn ($allocs, $projectId) => [
                    'project_id' => $projectId,
                    'project_code' => $allocs->first()->project->code ?? null,
                    'project_title' => $allocs->first()->project->title ?? null,
                    'total_hours' => $allocs->sum('allocated_hours'),
                ])
                ->values()
                ->toArray();

            $memberData[] = [
                'id' => $member->id,
                'name' => $member->name,
                'period_allocated' => $periodAllocated,
                'projects' => $projects,
            ];
        }

        // Add untracked row
        $untrackedAllocations = $allocations->whereNull('team_member_id');
        if ($untrackedAllocations->isNotEmpty()) {
            $untrackedProjects = $untrackedAllocations
                ->groupBy('project_id')
                ->map(fn ($allocs, $projectId) => [
                    'project_id' => $projectId,
                    'project_code' => $allocs->first()->project->code ?? null,
                    'project_title' => $allocs->first()->project->title ?? null,
                    'total_hours' => $allocs->sum('allocated_hours'),
                ])
                ->values()
                ->toArray();

            $memberData[] = [
                'id' => null,
                'name' => 'Untracked',
                'period_allocated' => $untrackedAllocations->sum('allocated_hours'),
                'projects' => $untrackedProjects,
            ];
        }

        return $memberData;
    }

    /**
     * Calculate aggregate metrics across all projects.
     *
     * @return array{
     *   total_planned: float,
     *   total_allocated: float,
     *   total_variance: float,
     *   status: string
     * }
     */
    private function calculateAggregates(array $projectData): array
    {
        $totalPlanned = array_sum(array_column($projectData, 'period_planned'));
        $totalAllocated = array_sum(array_column($projectData, 'period_allocated'));
        $totalVariance = $totalAllocated - $totalPlanned;
        $status = $this->varianceCalculator->determineStatus($totalVariance);

        return [
            'total_planned' => $totalPlanned,
            'total_allocated' => $totalAllocated,
            'total_variance' => $totalVariance,
            'status' => $status,
        ];
    }
}
