<?php

namespace App\Services\Promotions;

use App\Models\Student;
use App\Models\StudentPromotionRecord;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

/**
 * Admin-side reads against `student_promotion_records` (plan sections 13
 * and 16). Always returns hydrated student / parent context so the
 * admin UI can render rows without N+1 queries.
 */
class PromotionQueryService
{
    /**
     * Filterable list of promotion records for admin views.
     *
     * Filters supported:
     *  - status: string|array
     *  - next_school_year: string
     *  - current_school_year: string
     *  - parent_id: int
     *  - student_id: int
     *  - search: string (matches student first/last name)
     *  - parent_action_required: bool (subset of parentActionableStatuses)
     *
     * Returns array<int, array<string, mixed>> when paginate=false,
     * otherwise a Laravel paginator instance.
     *
     * @param array{
     *   status?: string|array<int,string>,
     *   next_school_year?: string|null,
     *   current_school_year?: string|null,
     *   parent_id?: int|null,
     *   student_id?: int|null,
     *   search?: string|null,
     *   parent_action_required?: bool,
     *   per_page?: int|null,
     * } $filters
     *
     * @return array{
     *   data: array<int,array<string,mixed>>,
     *   total: int,
     *   page: int,
     *   per_page: int,
     *   total_pages: int,
     *   counts_by_status: array<string,int>
     * }
     */
    public function list(array $filters): array
    {
        $query = StudentPromotionRecord::query();
        $this->applyFilters($query, $filters);

        $perPage = isset($filters['per_page']) ? max(1, min(200, (int) $filters['per_page'])) : 50;
        $page = isset($filters['page']) ? max(1, (int) $filters['page']) : 1;

        /** @var LengthAwarePaginator $paginator */
        $paginator = $query->orderByDesc('promotion_id')->paginate($perPage, ['*'], 'page', $page);

        /** @var EloquentCollection<int,StudentPromotionRecord> $records */
        $records = $paginator->getCollection();
        $studentIds = $records->pluck('student_id')->unique()->values()->all();
        $parentIds = $records->pluck('parent_id')->filter()->unique()->values()->all();

        $studentsById = !empty($studentIds)
            ? Student::query()->whereIn('id', $studentIds)->get()->keyBy('id')
            : new EloquentCollection();
        $parentsById = !empty($parentIds)
            ? User::query()->whereIn('id', $parentIds)->get()->keyBy('id')
            : new EloquentCollection();

        $rows = $records->map(function (StudentPromotionRecord $record) use ($studentsById, $parentsById) {
            return $this->presentAdminRow(
                $record,
                $studentsById->get($record->student_id),
                $record->parent_id ? $parentsById->get($record->parent_id) : null
            );
        })->all();

        return [
            'data' => array_values($rows),
            'total' => (int) $paginator->total(),
            'page' => (int) $paginator->currentPage(),
            'per_page' => (int) $paginator->perPage(),
            'total_pages' => (int) $paginator->lastPage(),
            'counts_by_status' => $this->countsByStatus($filters),
        ];
    }

    /**
     * Returns the per-status count of promotion records that match the
     * given filter set (ignoring `status` and pagination filters). Plan
     * section 16's reports rely on these counts.
     *
     * @return array<string,int>
     */
    public function countsByStatus(array $filters): array
    {
        $query = StudentPromotionRecord::query();
        // Apply non-status filters only.
        $filtersSansStatus = $filters;
        unset($filtersSansStatus['status'], $filtersSansStatus['parent_action_required']);
        $this->applyFilters($query, $filtersSansStatus);

        $rows = $query
            ->select('promotion_status', DB::raw('count(*) as total'))
            ->groupBy('promotion_status')
            ->get();

        $counts = array_fill_keys(StudentPromotionRecord::ALL_STATUSES, 0);
        foreach ($rows as $row) {
            $counts[(string) $row->promotion_status] = (int) $row->total;
        }
        return $counts;
    }

    /**
     * Hydrates a single record with student + parent context for the
     * admin detail view.
     */
    public function detail(StudentPromotionRecord $record): array
    {
        $student = Student::query()->find($record->student_id);
        $parent = $record->parent_id ? User::query()->find($record->parent_id) : null;

        return $this->presentAdminRow($record, $student, $parent, true);
    }

    /**
     * @param Builder<StudentPromotionRecord> $query
     */
    private function applyFilters(Builder $query, array $filters): void
    {
        if (!empty($filters['status'])) {
            $statuses = is_array($filters['status']) ? $filters['status'] : [$filters['status']];
            $statuses = array_values(array_filter($statuses, static fn ($s) => is_string($s) && $s !== ''));
            if (!empty($statuses)) {
                $query->whereIn('promotion_status', $statuses);
            }
        }
        if (!empty($filters['parent_action_required'])) {
            $query->whereIn('promotion_status', StudentPromotionRecord::parentActionableStatuses());
        }
        if (!empty($filters['next_school_year'])) {
            $query->where('next_school_year', $filters['next_school_year']);
        }
        if (!empty($filters['current_school_year'])) {
            $query->where('current_school_year', $filters['current_school_year']);
        }
        if (!empty($filters['parent_id'])) {
            $query->where('parent_id', (int) $filters['parent_id']);
        }
        if (!empty($filters['student_id'])) {
            $query->where('student_id', (int) $filters['student_id']);
        }
        if (!empty($filters['search'])) {
            $search = '%' . strtolower((string) $filters['search']) . '%';
            $query->whereIn('student_id', function ($sub) use ($search) {
                $sub->select('id')
                    ->from('students')
                    ->where(function ($w) use ($search) {
                        $w->whereRaw('LOWER(firstname) LIKE ?', [$search])
                            ->orWhereRaw('LOWER(lastname) LIKE ?', [$search]);
                    });
            });
        }
    }

    private function presentAdminRow(
        StudentPromotionRecord $record,
        ?Student $student,
        ?User $parent,
        bool $detail = false
    ): array {
        $row = [
            'promotion_id' => (int) $record->getKey(),
            'student_id' => (int) $record->student_id,
            'student_name' => $student
                ? trim((string) $student->firstname . ' ' . (string) $student->lastname)
                : null,
            'school_id' => $student->school_id ?? null,
            'parent_id' => $record->parent_id ? (int) $record->parent_id : null,
            'parent_name' => $parent
                ? trim((string) $parent->firstname . ' ' . (string) $parent->lastname)
                : null,
            'parent_email' => $parent->email ?? null,
            'current_school_year' => $record->current_school_year,
            'next_school_year' => $record->next_school_year,
            'current_level' => $record->current_level_name,
            'promoted_level' => $record->promoted_level_name,
            'promotion_status' => $record->promotion_status,
            'enrollment_status' => $record->enrollment_status,
            'enrollment_deadline' => $record->enrollment_deadline?->toDateString(),
            'parent_notified_at' => $record->parent_notified_at?->toDateTimeString(),
            'enrollment_started_at' => $record->enrollment_started_at?->toDateTimeString(),
            'enrollment_completed_at' => $record->enrollment_completed_at?->toDateTimeString(),
            'promotion_finalized_at' => $record->promotion_finalized_at?->toDateTimeString(),
            'final_average' => $record->final_average !== null ? (float) $record->final_average : null,
            'passed_current_level' => $record->passed_current_level,
            'updated_by' => $record->updated_by ? (int) $record->updated_by : null,
            'updated_at' => $record->updated_at?->toDateTimeString(),
        ];

        if ($detail) {
            $row['checklist'] = [
                'info_confirmed' => (bool) $record->info_confirmed,
                'documents_uploaded' => (bool) $record->documents_uploaded,
                'agreement_accepted' => (bool) $record->agreement_accepted,
                'payment_completed' => (bool) $record->payment_completed,
            ];
            $row['eligibility_notes'] = $record->eligibility_notes;
            $row['enrollment_id'] = $record->enrollment_id ? (int) $record->enrollment_id : null;
        }

        return $row;
    }
}
