<?php

namespace App\Services\Fees;

use App\Models\ClassSection;
use Illuminate\Support\Facades\Log;

class FeeStudentFeeService
{
    public const CATEGORY_FIRST_REGULAR = 'first_regular';
    public const CATEGORY_ADDITIONAL_REGULAR = 'additional_regular';
    public const CATEGORY_YOUTH = 'youth';
    public const CATEGORY_NOT_BILLABLE = 'not_billable';

    private const BILLABLE_ENROLLMENT_STATUSES = ['enrolled', 'payment pending'];
    private const WITHDRAWN_ENROLLMENT_STATUSES = ['withdrawn', 'refund pending', 'withdraw under review'];
    private const ACCEPTED_ADMISSION_STATUS = 'accepted';

    public function __construct(
        private FeeGradeService $grades,
        private FeeConfigService $config
    ) {
    }

    /**
     * Normalize grades, sort by grade level, and assign a tuition fee to each
     * student so that downstream callers (refund, balance, invoice) can read
     * the stored fee directly from the student record.
     *
     * Each student gets the following keys mutated/added:
     *   - grade           (normalized)
     *   - grade_level
     *   - fee_category    (first_regular | additional_regular | youth)
     *   - tuition_fee
     */
    public function assignFees(array $students): array
    {
        $fees = $this->config->getFees();
        $firstFee = (float) $fees['first_student_fee'];
        $secondFee = (float) $fees['second_student_fee'];
        $youthFee = (float) $fees['youth_fee'];

        foreach ($students as &$student) {
            $student['grade'] = $this->resolveGrade($student);
            $student['grade_level'] = $this->grades->getGradeLevel($student['grade']);
        }
        unset($student);

        usort($students, function ($a, $b) {
            return $this->grades->compareGrades($a['grade'] ?? '', $b['grade'] ?? '');
        });

        $regularCount = 0;
        foreach ($students as &$student) {
            $gradeLevel = (int) ($student['grade_level'] ?? 999);
            if ($gradeLevel > 9) {
                $student['fee_category'] = self::CATEGORY_YOUTH;
                $student['tuition_fee'] = $youthFee;
            } else {
                if ($regularCount === 0) {
                    $student['fee_category'] = self::CATEGORY_FIRST_REGULAR;
                    $student['tuition_fee'] = $firstFee;
                } else {
                    $student['fee_category'] = self::CATEGORY_ADDITIONAL_REGULAR;
                    $student['tuition_fee'] = $secondFee;
                }
                $regularCount++;
            }
        }
        unset($student);

        return $students;
    }

    public function totalTuitionFee(array $students): float
    {
        $students = $this->assignFees($students);

        $total = 0.0;
        foreach ($students as $student) {
            $total += (float) ($student['tuition_fee'] ?? 0);
        }

        return $total;
    }

    /**
     * Return a detailed student-level + family-level tuition breakdown.
     *
     * Only students that are accepted and either enrolled or payment-pending
     * are billed. Withdrawn students appear in the breakdown with a zero
     * tuition fee and the not_billable category so callers (refund service,
     * invoice generation) can still see them in one consistent payload.
     *
     * @return array{
     *     family_total: float,
     *     billable_count: int,
     *     non_billable_count: int,
     *     students: array<int, array<string, mixed>>,
     *     fees: array<string, float>
     * }
     */
    public function calculateBreakdown(array $students): array
    {
        $fees = $this->config->getFees();

        $billable = [];
        $nonBillable = [];

        foreach ($students as $index => $student) {
            $student['_original_index'] = $index;
            $student['grade'] = $this->resolveGrade($student);
            $student['grade_level'] = $this->grades->getGradeLevel($student['grade']);
            $student['enrollment_status'] = isset($student['enrollment_status'])
                ? strtolower((string) $student['enrollment_status'])
                : null;
            $student['admission_status'] = isset($student['admission_status'])
                ? strtolower((string) $student['admission_status'])
                : null;

            if ($this->isBillable($student)) {
                $billable[] = $student;
            } else {
                $nonBillable[] = $student;
            }
        }

        $billable = $this->assignFees($billable);

        $nonBillable = array_map(function (array $student): array {
            $student['fee_category'] = self::CATEGORY_NOT_BILLABLE;
            $student['tuition_fee'] = 0.0;
            return $student;
        }, $nonBillable);

        $combined = array_merge($billable, $nonBillable);
        usort($combined, fn ($a, $b) => ($a['_original_index'] ?? 0) <=> ($b['_original_index'] ?? 0));

        $familyTotal = 0.0;
        $studentRows = [];
        foreach ($combined as $student) {
            unset($student['_original_index']);

            $tuitionFee = (float) ($student['tuition_fee'] ?? 0);
            $discount = (float) ($student['discount_amount'] ?? 0);
            $finalTuition = max(0.0, $tuitionFee - $discount);
            $familyTotal += $finalTuition;

            $studentRows[] = [
                'student_id' => $student['student_id'] ?? null,
                'student_name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
                'grade' => $student['grade'] ?? null,
                'grade_level' => $student['grade_level'] ?? null,
                'enrollment_status' => $student['enrollment_status'] ?? null,
                'admission_status' => $student['admission_status'] ?? null,
                'fee_category' => $student['fee_category'] ?? null,
                'tuition_fee' => round($tuitionFee, 2),
                'discount_amount' => round($discount, 2),
                'final_tuition_amount' => round($finalTuition, 2),
            ];
        }

        Log::info('Tuition breakdown calculated', [
            'total_students' => count($studentRows),
            'billable' => count($billable),
            'non_billable' => count($nonBillable),
            'family_total' => round($familyTotal, 2),
        ]);

        return [
            'family_total' => round($familyTotal, 2),
            'billable_count' => count($billable),
            'non_billable_count' => count($nonBillable),
            'students' => $studentRows,
            'fees' => [
                'first_student_fee' => (float) $fees['first_student_fee'],
                'second_student_fee' => (float) $fees['second_student_fee'],
                'youth_fee' => (float) $fees['youth_fee'],
            ],
        ];
    }

    public function isBillable(array $student): bool
    {
        $status = strtolower((string) ($student['enrollment_status'] ?? ''));
        $admission = strtolower((string) ($student['admission_status'] ?? ''));

        return in_array($status, self::BILLABLE_ENROLLMENT_STATUSES, true)
            && $admission === self::ACCEPTED_ADMISSION_STATUS;
    }

    public function isWithdrawn(array $student): bool
    {
        $status = strtolower((string) ($student['enrollment_status'] ?? ''));
        return in_array($status, self::WITHDRAWN_ENROLLMENT_STATUSES, true);
    }

    private function resolveGrade(array $student): string
    {
        $grade = $student['grade'] ?? null;
        if ($grade === null || trim((string) $grade) === '') {
            $sectionId = $student['class_section_id'] ?? null;
            $grade = $sectionId ? ClassSection::getClassSectionNameBySectionId($sectionId) : null;
        }

        return $this->grades->normalizeGrade($grade);
    }
}
