<?php

namespace App\Http\Controllers\Api\Administrator;

use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Promotions\AdminListPromotionsRequest;
use App\Http\Requests\Promotions\EvaluateEligibilityRequest;
use App\Http\Requests\Promotions\ParentEnrollmentStepRequest;
use App\Http\Requests\Promotions\SendReminderRequest;
use App\Http\Requests\Promotions\SetEnrollmentDeadlineRequest;
use App\Http\Requests\Promotions\UpdatePromotionStatusRequest;
use App\Http\Requests\Promotions\UpsertLevelProgressionRequest;
use App\Http\Resources\Promotions\LevelProgressionResource;
use App\Http\Resources\Promotions\PromotionAuditLogResource;
use App\Http\Resources\Promotions\PromotionReminderResource;
use App\Http\Resources\Promotions\StudentPromotionResource;
use App\Models\PromotionAuditLog;
use App\Models\PromotionReminderLog;
use App\Models\StudentPromotionRecord;
use App\Services\Promotions\LevelProgressionService;
use App\Services\Promotions\PromotionAuditService;
use App\Services\Promotions\PromotionEligibilityService;
use App\Services\Promotions\PromotionEnrollmentService;
use App\Services\Promotions\PromotionQueryService;
use App\Services\Promotions\PromotionReminderService;
use App\Services\Promotions\PromotionStatusService;
use App\Services\Promotions\Placement\SectionPlacementPreviewService;
use App\Models\SectionPlacementBatch;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

/**
 * Admin / registrar API for managing the promotion + parent enrollment
 * lifecycle (plan sections 13, 16, 17).
 */
class AdministratorPromotionController extends BaseApiController
{
    public function __construct(
        private PromotionQueryService $query,
        private PromotionEligibilityService $eligibility,
        private PromotionStatusService $statusService,
        private PromotionEnrollmentService $enrollmentService,
        private PromotionReminderService $reminders,
        private PromotionAuditService $audit,
        private LevelProgressionService $progression,
        private SectionPlacementPreviewService $sectionPlacement
    ) {
        parent::__construct();
    }

    public function index(AdminListPromotionsRequest $request): JsonResponse
    {
        $result = $this->query->list($request->filters());
        return response()->json([
            'ok' => true,
            'data' => StudentPromotionResource::collection($result['data']),
            'pagination' => [
                'page' => $result['page'],
                'per_page' => $result['per_page'],
                'total' => $result['total'],
                'total_pages' => $result['total_pages'],
            ],
            'counts_by_status' => $result['counts_by_status'],
        ]);
    }

    public function show(int $promotionId): JsonResponse
    {
        $record = $this->findOrFail($promotionId);
        if ($record instanceof JsonResponse) {
            return $record;
        }
        return response()->json([
            'ok' => true,
            'data' => new StudentPromotionResource($this->query->detail($record)),
        ]);
    }

    public function summary(AdminListPromotionsRequest $request): JsonResponse
    {
        $counts = $this->query->countsByStatus($request->filters());
        return response()->json([
            'ok' => true,
            'counts_by_status' => $counts,
            'total' => array_sum($counts),
        ]);
    }

    public function evaluate(EvaluateEligibilityRequest $request): JsonResponse
    {
        $payload = $request->validated();
        $userId = $this->getCurrentUserId();
        $scope = $payload['scope'] ?? 'school_year';

        try {
            $result = match ($scope) {
                'student' => [
                    'scope' => 'student',
                    'record' => optional(
                        $this->eligibility->evaluateStudent(
                            (int) $payload['student_id'],
                            $payload['current_school_year'] ?? null,
                            $userId
                        )
                    )?->toArray(),
                ],
                'class_section' => array_merge(
                    ['scope' => 'class_section'],
                    $this->eligibility->evaluateClassSection(
                        (int) $payload['class_section_id'],
                        $payload['current_school_year'] ?? null,
                        $userId
                    )
                ),
                default => array_merge(
                    ['scope' => 'school_year'],
                    $this->eligibility->evaluateSchoolYear(
                        $payload['current_school_year'] ?? null,
                        $userId
                    )
                ),
            };
        } catch (\Throwable $e) {
            Log::error('Promotion eligibility evaluation failed', ['exception' => $e]);
            return response()->json([
                'ok' => false,
                'message' => 'Failed to evaluate promotion eligibility.',
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        return response()->json([
            'ok' => true,
            'result' => $result,
        ]);
    }


    public function createPlacementPreview(Request $request): JsonResponse
    {
        $payload = $request->validate([
            'from_school_year' => ['required', 'string', 'max:25'],
            'to_school_year' => ['required', 'string', 'max:25'],
            'from_grade_level_id' => ['nullable', 'integer', 'min:1'],
            'to_grade_level_id' => ['required', 'integer', 'min:1'],
        ]);

        try {
            $batch = $this->sectionPlacement->createDraft(
                $payload['from_school_year'],
                $payload['to_school_year'],
                $payload['from_grade_level_id'] ?? null,
                $payload['to_grade_level_id'] ?? null,
                $this->getCurrentUserId()
            );
        } catch (\Throwable $e) {
            Log::error('Section placement preview generation failed', ['exception' => $e]);
            return response()->json([
                'ok' => false,
                'message' => $e->getMessage(),
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        return response()->json([
            'ok' => true,
            'data' => $this->sectionPlacement->previewPayload($batch),
        ]);
    }

    public function showPlacementBatch(int $batchId): JsonResponse
    {
        $batch = SectionPlacementBatch::query()->find($batchId);
        if (!$batch) {
            return response()->json(['ok' => false, 'message' => 'Placement batch not found.'], Response::HTTP_NOT_FOUND);
        }

        return response()->json([
            'ok' => true,
            'data' => $this->sectionPlacement->previewPayload($batch),
        ]);
    }

    public function finalizePlacementBatch(int $batchId): JsonResponse
    {
        try {
            $batch = $this->sectionPlacement->finalize($batchId, $this->getCurrentUserId());
        } catch (\Throwable $e) {
            Log::error('Section placement finalization failed', ['batch_id' => $batchId, 'exception' => $e]);
            return response()->json([
                'ok' => false,
                'message' => $e->getMessage(),
            ], Response::HTTP_CONFLICT);
        }

        return response()->json([
            'ok' => true,
            'data' => $this->sectionPlacement->previewPayload($batch),
        ]);
    }

    public function updateStatus(UpdatePromotionStatusRequest $request, int $promotionId): JsonResponse
    {
        $record = $this->findOrFail($promotionId);
        if ($record instanceof JsonResponse) {
            return $record;
        }

        $payload = $request->validated();
        $userId = $this->getCurrentUserId();

        try {
            $updated = !empty($payload['force'])
                ? $this->statusService->forceStatus($record, $payload['status'], $userId, $payload['notes'] ?? null)
                : $this->statusService->transition($record, $payload['status'], $userId, $payload['notes'] ?? null);
        } catch (\InvalidArgumentException $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
        } catch (\RuntimeException $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], Response::HTTP_CONFLICT);
        }

        return response()->json([
            'ok' => true,
            'data' => new StudentPromotionResource($this->query->detail($updated->refresh())),
        ]);
    }

    public function setDeadline(SetEnrollmentDeadlineRequest $request, ?int $promotionId = null): JsonResponse
    {
        $payload = $request->validated();
        $userId = $this->getCurrentUserId();
        $newDeadline = $payload['enrollment_deadline'];
        $applyTo = $payload['apply_to'] ?? ($promotionId ? 'single' : 'school_year');

        $records = collect();
        if ($promotionId !== null || $applyTo === 'single') {
            $id = $promotionId ?? null;
            if ($id === null) {
                return response()->json(['ok' => false, 'message' => 'promotion_id is required for single deadline updates.'], Response::HTTP_UNPROCESSABLE_ENTITY);
            }
            $record = $this->findOrFail($id);
            if ($record instanceof JsonResponse) {
                return $record;
            }
            $records = collect([$record]);
        } elseif ($applyTo === 'filter' && !empty($payload['promotion_ids'])) {
            $records = StudentPromotionRecord::query()
                ->whereIn('promotion_id', $payload['promotion_ids'])
                ->get();
        } else {
            $year = $payload['next_school_year'] ?? null;
            if ($year === null || $year === '') {
                return response()->json([
                    'ok' => false,
                    'message' => 'next_school_year is required when applying a bulk deadline.',
                ], Response::HTTP_UNPROCESSABLE_ENTITY);
            }
            $records = StudentPromotionRecord::query()
                ->forNextYear($year)
                ->whereIn('promotion_status', StudentPromotionRecord::openStatuses())
                ->get();
        }

        $updatedIds = [];
        foreach ($records as $record) {
            $oldDeadline = $record->enrollment_deadline?->toDateString();
            DB::transaction(function () use ($record, $newDeadline, $oldDeadline, $userId, &$updatedIds) {
                $record->enrollment_deadline = $newDeadline;
                $record->updated_by = $userId;
                $record->save();
                $this->audit->logDeadlineSet($record, $oldDeadline, $newDeadline, $userId);
                $updatedIds[] = (int) $record->getKey();
            });
        }

        return response()->json([
            'ok' => true,
            'updated' => count($updatedIds),
            'promotion_ids' => $updatedIds,
        ]);
    }

    public function sendReminder(SendReminderRequest $request, int $promotionId): JsonResponse
    {
        $record = $this->findOrFail($promotionId);
        if ($record instanceof JsonResponse) {
            return $record;
        }

        $payload = $request->validated();
        $userId = $this->getCurrentUserId();

        try {
            $log = $this->reminders->send(
                $record,
                $payload['reminder_type'] ?? PromotionReminderLog::TYPE_MANUAL,
                $userId,
                $payload['subject'] ?? null,
                $payload['message'] ?? null,
                $payload['channels'] ?? ['in_app', 'email']
            );
        } catch (\Throwable $e) {
            Log::error('Promotion reminder send failed', [
                'promotion_id' => $promotionId,
                'exception' => $e,
            ]);
            return response()->json([
                'ok' => false,
                'message' => 'Failed to send reminder.',
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        return response()->json([
            'ok' => true,
            'reminder' => new PromotionReminderResource($log),
        ]);
    }

    public function dispatchScheduledReminders(): JsonResponse
    {
        $userId = $this->getCurrentUserId();
        $result = $this->reminders->dispatchScheduledReminders(null, $userId);
        return response()->json([
            'ok' => true,
            'result' => $result,
        ]);
    }

    public function reminders(int $promotionId): JsonResponse
    {
        $record = $this->findOrFail($promotionId);
        if ($record instanceof JsonResponse) {
            return $record;
        }
        $rows = PromotionReminderLog::query()
            ->forPromotion((int) $record->getKey())
            ->orderByDesc('id')
            ->get();
        return response()->json([
            'ok' => true,
            'reminders' => PromotionReminderResource::collection($rows),
        ]);
    }

    public function audit(int $promotionId): JsonResponse
    {
        $record = $this->findOrFail($promotionId);
        if ($record instanceof JsonResponse) {
            return $record;
        }
        $rows = PromotionAuditLog::query()
            ->forPromotion((int) $record->getKey())
            ->orderByDesc('id')
            ->get();
        return response()->json([
            'ok' => true,
            'entries' => PromotionAuditLogResource::collection($rows),
        ]);
    }

    public function expireDeadlines(): JsonResponse
    {
        $result = $this->enrollmentService->markExpiredDeadlines(null, $this->getCurrentUserId());
        return response()->json([
            'ok' => true,
            'result' => $result,
        ]);
    }

    public function adminCompleteEnrollment(ParentEnrollmentStepRequest $request, int $promotionId): JsonResponse
    {
        $record = $this->findOrFail($promotionId);
        if ($record instanceof JsonResponse) {
            return $record;
        }
        $userId = $this->getCurrentUserId();
        $parentId = (int) $record->parent_id ?: $userId ?: 0;

        if ($parentId <= 0) {
            return response()->json([
                'ok' => false,
                'message' => 'Promotion record has no associated parent.',
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        try {
            $updated = $this->enrollmentService->updateSteps(
                $record,
                $parentId,
                $request->steps(),
                $userId
            );
        } catch (\RuntimeException $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], Response::HTTP_CONFLICT);
        }

        return response()->json([
            'ok' => true,
            'data' => new StudentPromotionResource($this->query->detail($updated->refresh())),
        ]);
    }

    public function levelProgressions(): JsonResponse
    {
        $rows = $this->progression->list(false);
        return response()->json([
            'ok' => true,
            'levels' => LevelProgressionResource::collection($rows),
        ]);
    }

    public function upsertLevelProgression(UpsertLevelProgressionRequest $request): JsonResponse
    {
        $row = $this->progression->upsertMapping($request->validated());
        return response()->json([
            'ok' => true,
            'level' => new LevelProgressionResource($row),
        ]);
    }

    public function seedLevelProgressions(): JsonResponse
    {
        $created = $this->progression->seedDefaults();
        return response()->json([
            'ok' => true,
            'created' => $created,
        ]);
    }

    private function findOrFail(int $promotionId): StudentPromotionRecord|JsonResponse
    {
        $record = StudentPromotionRecord::query()->find($promotionId);
        if (!$record) {
            return response()->json([
                'ok' => false,
                'message' => 'Promotion record not found.',
            ], Response::HTTP_NOT_FOUND);
        }
        return $record;
    }
}
