<?php

namespace App\Http\Controllers\View;

use App\Http\Controllers\Controller;
use App\Models\ClassPrepAdjustment;
use App\Models\ClassPreparationLog;
use App\Models\ClassSection;
use App\Models\Configuration;
use App\Models\StudentClass;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;

class ClassPreparationController extends Controller
{
    protected StudentClass $studentClass;
    protected ClassPreparationLog $prepLog;
    protected ClassSection $classSection;
    protected Configuration $config;
    protected ClassPrepAdjustment $adjustment;
    protected ?string $schoolYear;
    protected ?string $semester;

    private array $allowedPrepCategories = [
        'Grade Box',
        'Large Table',
        'Regular Chair',
        'Small Chair',
        'Small Table',
        'Teacher Chair',
        'Trash Bin',
        'White Board',
    ];

    public function __construct(
        StudentClass $studentClass,
        ClassPreparationLog $prepLog,
        ClassSection $classSection,
        Configuration $config,
        ClassPrepAdjustment $adjustment
    ) {
        $this->studentClass = $studentClass;
        $this->prepLog      = $prepLog;
        $this->classSection = $classSection;
        $this->config       = $config;
        $this->adjustment   = $adjustment;

        $this->schoolYear = $this->config->getConfig('school_year');
        $this->semester   = $this->config->getConfig('semester');
    }

    public function index(Request $request): View
    {
        $schoolYear = (string) ($request->query('school_year') ?? $this->schoolYear ?? '');
        $semParam   = $request->query('semester');
        $semester   = is_string($semParam) && $semParam !== '' ? $semParam : (string) ($this->semester ?? '');
        $allowed    = $this->allowedPrepCategories;

        $classSections = $this->studentClass->newQuery()
            ->selectRaw('class_section_id, COUNT(DISTINCT student_id) AS student_count')
            ->where('school_year', $schoolYear)
            ->where('semester', $semester)
            ->groupBy('class_section_id')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->toArray();

        $inventoryMap = $this->buildInventoryMap($schoolYear, $semester);

        $requiredTotals = array_fill_keys($allowed, 0);
        $prepResults    = [];

        foreach ($classSections as $section) {
            $classSectionId = (string) ($section['class_section_id'] ?? '');
            $studentCount   = (int) ($section['student_count'] ?? 0);
            $classLevel     = $this->getClassLevelBySection($classSectionId);
            $className      = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId;

            $baseItems = $this->calculatePrepItems($studentCount, $classLevel, $classSectionId);

            $rawAdjustments = $this->adjustment->newQuery()
                ->where('class_section_id', $classSectionId)
                ->where('school_year', $schoolYear)
                ->where('adjustable', 1)
                ->get()
                ->map(fn ($row) => (array) $row)
                ->toArray();

            $adjMap = ['Large Table' => 0, 'Small Table' => 0];

            foreach ($rawAdjustments as $adjustment) {
                $item  = $adjustment['item_name'] ?? '';
                $delta = (int) ($adjustment['adjustment'] ?? 0);

                if (array_key_exists($item, $adjMap)) {
                    $adjMap[$item] = $delta;
                }

                if (isset($baseItems[$item])) {
                    $baseItems[$item] = max(0, (int) $baseItems[$item] + $delta);
                } elseif (in_array($item, $allowed, true)) {
                    $baseItems[$item] = max(0, $delta);
                }
            }

            foreach ($allowed as $category) {
                $requiredTotals[$category] += (int) ($baseItems[$category] ?? 0);
            }

            $oldSnap = $this->prepLog->newQuery()
                ->where('class_section_id', $classSectionId)
                ->where('school_year', $schoolYear)
                ->orderBy('created_at', 'DESC')
                ->first();

            $oldPrep    = $oldSnap ? json_decode($oldSnap['prep_data'] ?? '[]', true) : [];
            $hasChanged = $this->hasPrepChanged($baseItems, $oldPrep);

            $prepResults[] = [
                'class_section'    => $className,
                'class_section_id' => $classSectionId,
                'student_count'    => $studentCount,
                'class_level'      => $classLevel,
                'prep_items'       => $baseItems,
                'needs_print'      => $hasChanged,
                'last_printed_at'  => $oldSnap['created_at'] ?? null,
                'adjustments'      => $adjMap,
            ];
        }

        $shortages = [];
        foreach ($requiredTotals as $item => $required) {
            $available = (int) ($inventoryMap[$item] ?? 0);
            if ($available < (int) $required) {
                $shortages[$item] = (int) $required - $available;
            }
        }

        return view('class_prep/list', [
            'prepResults' => $prepResults,
            'schoolYear'  => $schoolYear,
            'semester'    => $semester,
            'shortages'   => $shortages,
            'totalNeeded' => $requiredTotals,
            'available'   => $inventoryMap,
        ]);
    }

    public function saveAdjustment(Request $request): RedirectResponse
    {
        $data       = $request->all();
        $sectionId  = (string) ($data['class_section_id'] ?? '');
        $schoolYear = (string) ($data['school_year'] ?? $this->schoolYear ?? '');
        $adjustments = is_array($data['adjustments'] ?? null) ? $data['adjustments'] : [];

        foreach ($adjustments as $itemName => $adjustment) {
            $row = $this->adjustment->newQuery()
                ->where('class_section_id', $sectionId)
                ->where('school_year', $schoolYear)
                ->where('item_name', $itemName)
                ->first();

            if ($row) {
                $this->adjustment->update($row['id'], [
                    'adjustment' => (int) $adjustment,
                    'adjustable' => 1,
                ]);
                continue;
            }

            $this->adjustment->insert([
                'class_section_id' => $sectionId,
                'item_name'        => $itemName,
                'adjustment'       => (int) $adjustment,
                'school_year'      => $schoolYear,
                'adjustable'       => 1,
            ]);
        }

        return redirect()->to('/class-prep?school_year=' . urlencode($schoolYear))
            ->with('success', 'Adjustments saved successfully.');
    }

    public function print(string $classSectionId, string $schoolYear): View
    {
        $className = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId;

        $studentRow = $this->studentClass->newQuery()
            ->selectRaw('COUNT(DISTINCT student_id) AS cnt')
            ->where('class_section_id', $classSectionId)
            ->where('school_year', $schoolYear)
            ->where('semester', $this->semester)
            ->first();

        $studentCount = (int) ($studentRow['cnt'] ?? 0);
        $classLevel   = $this->getClassLevelBySection($classSectionId);
        $items        = $this->calculatePrepItems($studentCount, $classLevel, $classSectionId);

        $rawAdjustments = $this->adjustment->newQuery()
            ->where('class_section_id', $classSectionId)
            ->where('school_year', $schoolYear)
            ->where('adjustable', 1)
            ->get()
            ->map(fn ($row) => (array) $row)
            ->toArray();

        foreach ($rawAdjustments as $adjustment) {
            $item  = $adjustment['item_name'] ?? '';
            $delta = (int) ($adjustment['adjustment'] ?? 0);
            if (isset($items[$item])) {
                $items[$item] = max(0, (int) $items[$item] + $delta);
            }
        }

        $this->prepLog->insert([
            'class_section_id' => $classSectionId,
            'class_section'    => $className,
            'school_year'      => $schoolYear,
            'prep_data'        => json_encode($items),
            'created_at'       => utc_now(),
        ]);

        return view('class_prep/print', [
            'classSectionId' => $classSectionId,
            'classSection'   => $className,
            'schoolYear'     => $schoolYear,
            'prepItems'      => $items,
        ]);
    }

    public function apiList(Request $request): JsonResponse
    {
        $schoolYear = (string) ($request->query('school_year') ?? $this->schoolYear ?? '');
        $allowed    = $this->allowedPrepCategories;

        $classSections = $this->studentClass->newQuery()
            ->selectRaw('class_section_id, COUNT(DISTINCT student_id) AS student_count')
            ->where('school_year', $schoolYear)
            ->where('semester', $this->semester)
            ->groupBy('class_section_id')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->toArray();

        $inventoryMap = $this->buildInventoryMap($schoolYear, $this->semester);

        $requiredTotals = array_fill_keys($allowed, 0);
        $results        = [];

        foreach ($classSections as $row) {
            $classSectionId = (string) ($row['class_section_id'] ?? '');
            $studentCount   = (int) ($row['student_count'] ?? 0);
            $classLevel     = $this->getClassLevelBySection($classSectionId);
            $className      = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId;

            $baseItems = $this->calculatePrepItems($studentCount, $classLevel, $classSectionId);

            $rawAdjustments = $this->adjustment->newQuery()
                ->where('class_section_id', $classSectionId)
                ->where('school_year', $schoolYear)
                ->where('adjustable', 1)
                ->get()
                ->map(fn ($row) => (array) $row)
                ->toArray();

            $adjMap = ['Large Table' => 0, 'Small Table' => 0];

            foreach ($rawAdjustments as $adjustment) {
                $item  = $adjustment['item_name'] ?? '';
                $delta = (int) ($adjustment['adjustment'] ?? 0);
                if (array_key_exists($item, $adjMap)) {
                    $adjMap[$item] = $delta;
                }
                if (isset($baseItems[$item])) {
                    $baseItems[$item] = max(0, (int) $baseItems[$item] + $delta);
                }
            }

            foreach ($allowed as $cat) {
                $requiredTotals[$cat] += (int) ($baseItems[$cat] ?? 0);
            }

            $oldSnap = $this->prepLog->newQuery()
                ->where('class_section_id', $classSectionId)
                ->where('school_year', $schoolYear)
                ->orderBy('created_at', 'DESC')
                ->first();

            $oldPrep    = $oldSnap ? json_decode($oldSnap['prep_data'] ?? '[]', true) : [];
            $hasChanged = $this->hasPrepChanged($baseItems, $oldPrep);

            $results[] = [
                'class_section'    => $className,
                'class_section_id' => $classSectionId,
                'student_count'    => $studentCount,
                'class_level'      => $classLevel,
                'prep_items'       => $baseItems,
                'needs_print'      => $hasChanged,
                'last_printed_at'  => $oldSnap['created_at'] ?? null,
                'adjustments'      => $adjMap,
            ];
        }

        $shortages = [];
        foreach ($requiredTotals as $item => $required) {
            $available = (int) ($inventoryMap[$item] ?? 0);
            if ($available < (int) $required) {
                $shortages[$item] = (int) $required - $available;
            }
        }

        return response()->json([
            'ok'          => true,
            'schoolYear'  => $schoolYear,
            'results'     => $results,
            'totals'      => $requiredTotals,
            'shortages'   => $shortages,
            'csrf_token'  => csrf_token(),
            'csrf_hash'   => csrf_token(),
        ]);
    }

    public function apiMarkPrinted(Request $request): JsonResponse
    {
        $schoolYear = (string) ($request->post('school_year') ?? $this->schoolYear ?? '');
        $ids        = $request->post('class_section_ids', []);
        if (!is_array($ids)) {
            $ids = $ids ? [$ids] : [];
        }
        $ids = array_values(array_unique(array_filter(array_map('strval', $ids))));

        $count = 0;
        foreach ($ids as $classSectionId) {
            $studentRow = $this->studentClass->newQuery()
                ->selectRaw('COUNT(DISTINCT student_id) AS cnt')
                ->where('class_section_id', $classSectionId)
                ->where('school_year', $schoolYear)
                ->where('semester', $this->semester)
                ->first();

            $studentCount = (int) ($studentRow['cnt'] ?? 0);
            $classLevel   = $this->getClassLevelBySection($classSectionId);
            $className    = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId;
            $items        = $this->calculatePrepItems($studentCount, $classLevel, $classSectionId);

            $rawAdjustments = $this->adjustment->newQuery()
                ->where('class_section_id', $classSectionId)
                ->where('school_year', $schoolYear)
                ->where('adjustable', 1)
                ->get()
                ->map(fn ($row) => (array) $row)
                ->toArray();

            foreach ($rawAdjustments as $adjustment) {
                $item  = $adjustment['item_name'] ?? '';
                $delta = (int) ($adjustment['adjustment'] ?? 0);
                if (isset($items[$item])) {
                    $items[$item] = max(0, (int) $items[$item] + $delta);
                }
            }

            try {
                $this->prepLog->insert([
                    'class_section_id' => $classSectionId,
                    'class_section'    => $className,
                    'school_year'      => $schoolYear,
                    'prep_data'        => json_encode($items),
                    'created_at'       => utc_now(),
                ]);
                $count++;
            } catch (\Throwable $e) {
                // ignore and continue to next section
            }
        }

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

    private function getTeacherCountForSection(string $classSectionId, ?string $schoolYear = null): int
    {
        $schoolYear = $schoolYear ?? $this->schoolYear;

        $row = DB::table('teacher_class')
            ->selectRaw('COUNT(*) AS cnt')
            ->where('class_section_id', $classSectionId)
            ->whereIn('position', ['main', 'ta'])
            ->where('school_year', $schoolYear)
            ->first();

        return (int) ($row->cnt ?? 0);
    }

    private function calculatePrepItems(int $students, int $classLevel, string $classSectionId): array
    {
        $allowed = $this->allowedPrepCategories;
        $items   = array_fill_keys($allowed, 0);

        $categories = DB::table('inventory_categories')
            ->select('name', 'grade_min', 'grade_max')
            ->where('type', 'classroom')
            ->whereIn('name', $allowed)
            ->orderBy('name')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->toArray();

        $isLowerGrades = in_array($classLevel, [1, 2], true);
        $teacherCount   = $this->getTeacherCountForSection($classSectionId);

        foreach ($categories as $category) {
            $name     = $category['name'] ?? '';
            $gradeMin = $category['grade_min'];
            $gradeMax = $category['grade_max'];

            if ($gradeMin !== null && $classLevel < (int) $gradeMin) {
                $items[$name] = 0;
                continue;
            }
            if ($gradeMax !== null && $classLevel > (int) $gradeMax) {
                $items[$name] = 0;
                continue;
            }

            $qty = 0;
            switch (strtolower($name)) {
                case 'small table':
                    $qty = $isLowerGrades ? (int) ceil($students / 3) : 0;
                    break;
                case 'large table':
                    $qty = $isLowerGrades ? 0 : (int) ceil($students / 4);
                    break;
                case 'small chair':
                    $qty = $isLowerGrades ? $students : 0;
                    break;
                case 'regular chair':
                    $qty = $isLowerGrades ? 0 : $students;
                    break;
                case 'teacher chair':
                    $qty = $teacherCount;
                    break;
                case 'trash bin':
                case 'white board':
                case 'grade box':
                    $qty = 1;
                    break;
                default:
                    $qty = 0;
            }

            if (array_key_exists($name, $items)) {
                $items[$name] = $qty;
            }
        }

        return $items;
    }

    private function hasPrepChanged(array $new, array $old): bool
    {
        ksort($new);
        ksort($old);

        return json_encode($new) !== json_encode($old);
    }

    private function buildInventoryMap(string $schoolYear, string $semester): array
    {
        $allowed       = $this->allowedPrepCategories;
        $inventoryMap = array_fill_keys($allowed, 0);

        $queries = [
            DB::table('inventory_items as ii')
                ->selectRaw('ic.name AS item_name, COALESCE(SUM(ii.good_qty),0) AS available')
                ->join('inventory_categories as ic', 'ic.id', '=', 'ii.category_id')
                ->where('ii.type', 'classroom')
                ->where('ii.school_year', $schoolYear)
                ->where('ii.semester', $semester)
                ->whereIn('ic.name', $allowed)
                ->groupBy('ic.name'),
            DB::table('inventory_items')
                ->selectRaw('name AS item_name, COALESCE(SUM(good_qty),0) AS available')
                ->where('type', 'classroom')
                ->where('school_year', $schoolYear)
                ->where('semester', $semester)
                ->whereIn('name', $allowed)
                ->groupBy('name'),
            DB::table('inventory_items as ii')
                ->selectRaw('ic.name AS item_name, COALESCE(SUM(CASE WHEN ii.`condition`="good" THEN ii.quantity ELSE 0 END),0) AS available')
                ->join('inventory_categories as ic', 'ic.id', '=', 'ii.category_id')
                ->where('ii.type', 'classroom')
                ->where('ii.school_year', $schoolYear)
                ->where('ii.semester', $semester)
                ->whereIn('ic.name', $allowed)
                ->groupBy('ic.name'),
            DB::table('inventory_items')
                ->selectRaw('name AS item_name, COALESCE(SUM(CASE WHEN `condition`="good" THEN quantity ELSE 0 END),0) AS available')
                ->where('type', 'classroom')
                ->where('school_year', $schoolYear)
                ->where('semester', $semester)
                ->whereIn('name', $allowed)
                ->groupBy('name'),
        ];

        foreach ($queries as $query) {
            foreach ($query->get()->toArray() as $row) {
                $row  = (array) $row;
                $name = (string) ($row['item_name'] ?? $row['name'] ?? '');
                $val  = (int) ($row['available'] ?? 0);

                if ($name !== '' && isset($inventoryMap[$name])) {
                    $inventoryMap[$name] = max($inventoryMap[$name], $val);
                }
            }
        }

        return $inventoryMap;
    }

    private function getClassLevelBySection(string $classSectionId): int
    {
        if (preg_match('/^(kg|k)(\b|[^a-z0-9])/i', $classSectionId)) {
            return 1;
        }

        $numeric = (int) preg_replace('/\D/', '', $classSectionId);
        if ($numeric > 0 && $numeric < 30) {
            $firstDigit = (int) substr((string) $numeric, 0, 1);
            return match ($firstDigit) {
                1 => 1,
                2 => 2,
                default => 3,
            };
        }

        return 3;
    }
}
