<?php

namespace App\Services\ClassProgress;

use App\Models\ClassProgressAttachment;
use App\Models\ClassProgressReport;
use App\Models\TeacherClass;
use App\Models\User;
use App\Services\SchoolYears\SchoolYearContextService;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ClassProgressMutationService
{
    public function __construct(
        private ClassProgressRuleService $rules,
        private ClassProgressAttachmentService $attachments,
        private ClassProgressQueryService $queries,
        private SchoolYearContextService $schoolYears,
    ) {}

    public function createReports(User $user, array $payload, array $filesBySubject): Collection
    {
        $subjectSections = (array) config('progress.subject_sections', []);
        $classSectionId = (int) ($payload['class_section_id'] ?? 0);

        $this->assertTeacherAssignment($user, $classSectionId);

        $schoolYear = $this->schoolYears->currentSchoolYear($payload['school_year'] ?? null);
        $semester = $this->schoolYears->currentSemester($payload['semester'] ?? null);

        $weekStart = (string) ($payload['week_start'] ?? '');
        $weekEnd = $this->rules->ensureWeekEnd($weekStart, $payload['week_end'] ?? null);

        if (!$this->rules->isWeekEndValid($weekStart, $weekEnd)) {
            throw new \InvalidArgumentException('Week end must be the same as or after the week start.');
        }

        if (!$this->rules->hasIslamicUnitSelection($payload)) {
            throw new \InvalidArgumentException('Please select at least one Islamic Studies unit.');
        }

        $confirmOverwrite = filter_var($payload['confirm_overwrite'] ?? false, FILTER_VALIDATE_BOOLEAN);
        $existingIds = ClassProgressReport::query()
            ->where('class_section_id', $classSectionId)
            ->whereDate('week_start', $weekStart)
            ->where('teacher_id', $user->id)
            ->pluck('id')
            ->map(fn ($id) => (int) $id)
            ->filter()
            ->values()
            ->all();

        if ($existingIds !== [] && !$confirmOverwrite) {
            throw new \InvalidArgumentException('CONFIRM_OVERWRITE');
        }

        $reports = DB::transaction(function () use ($user, $payload, $subjectSections, $classSectionId, $schoolYear, $semester, $weekStart, $weekEnd, $filesBySubject, $confirmOverwrite, $existingIds) {
            if ($existingIds !== [] && $confirmOverwrite) {
                ClassProgressAttachment::query()->whereIn('report_id', $existingIds)->delete();
                ClassProgressReport::query()->whereIn('id', $existingIds)->delete();
            }

            $created = collect();

            foreach ($subjectSections as $slug => $section) {
                $covered = trim((string) ($payload["covered_{$slug}"] ?? ''));
                if ($covered === '') {
                    continue;
                }

                $homework = trim((string) ($payload["homework_{$slug}"] ?? ''));
                $unitValues = (array) ($payload["unit_{$slug}"] ?? []);
                $chapterValues = (array) ($payload["chapter_{$slug}"] ?? []);
                $unitTitle = $this->rules->buildUnitChapterSummary($unitValues, $chapterValues);

                $data = [
                    'teacher_id' => $user->id,
                    'class_section_id' => $classSectionId,
                    'week_start' => $weekStart,
                    'week_end' => $weekEnd,
                    'subject' => $section['db_subject'] ?? $section['label'] ?? $slug,
                    'unit_title' => $unitTitle,
                    'covered' => $covered,
                    'homework' => $homework !== '' ? $homework : null,
                    'status' => $this->rules->defaultStatus(),
                    'support_needed' => $payload['support_needed'] ?? null,
                    'flags_json' => $this->rules->normalizeFlags($payload['flags'] ?? null),
                ];

                if (Schema::hasColumn('class_progress_reports', 'school_year')) {
                    $data['school_year'] = $schoolYear !== '' ? $schoolYear : null;
                }
                if (Schema::hasColumn('class_progress_reports', 'semester')) {
                    $data['semester'] = $semester !== '' ? $semester : null;
                }

                $report = ClassProgressReport::query()->create($data);

                $attachments = $filesBySubject[$slug] ?? [];
                $stored = $this->attachments->storeAttachments($report->id, $attachments);
                if ($stored !== []) {
                    $report->update(['attachment_path' => $stored[0]['file_path'] ?? null]);
                }

                $created->push($report);
            }

            if ($created->isEmpty()) {
                throw new \InvalidArgumentException('Please provide progress for at least one subject.');
            }

            return $created;
        });

        Log::info('Class progress reports created.', [
            'teacher_id' => $user->id,
            'class_section_id' => $classSectionId,
            'count' => $reports->count(),
        ]);

        return $reports;
    }

    /**
     * legacy `ClassProgressController::update` — sync all subjects for the weekly batch keyed by {@see $anchor}.
     *
     * @return Collection<int, ClassProgressReport>
     */
    public function syncWeeklyFromTeacherForm(User $user, ClassProgressReport $anchor, array $payload, array $filesBySubject): Collection
    {
        $subjectSections = (array) config('progress.subject_sections', []);
        $classSectionId = (int) ($payload['class_section_id'] ?? $anchor->class_section_id);

        $this->assertTeacherAssignment($user, $classSectionId);

        $schoolYear = $this->schoolYears->currentSchoolYear($payload['school_year'] ?? null);
        $semester = $this->schoolYears->currentSemester($payload['semester'] ?? null);

        $weekStart = (string) ($payload['week_start'] ?? '');
        $weekEnd = $this->rules->ensureWeekEnd($weekStart, $payload['week_end'] ?? null);

        if (! $this->rules->isWeekEndValid($weekStart, $weekEnd)) {
            throw new \InvalidArgumentException('Week end must be the same as or after the week start.');
        }

        if (! $this->rules->hasIslamicUnitSelection($payload)) {
            throw new \InvalidArgumentException('Please select at least one Islamic Studies unit.');
        }

        $originalWeekStart = $anchor->week_start instanceof \Carbon\CarbonInterface
            ? $anchor->week_start->format('Y-m-d')
            : (string) $anchor->week_start;

        $confirmOverwrite = filter_var($payload['confirm_overwrite'] ?? false, FILTER_VALIDATE_BOOLEAN);

        if ($weekStart !== '' && $weekStart !== $originalWeekStart) {
            $conflicts = ClassProgressReport::query()
                ->where('class_section_id', $classSectionId)
                ->whereDate('week_start', $weekStart)
                ->where('teacher_id', $user->id)
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->filter()
                ->values()
                ->all();

            if ($conflicts !== [] && ! $confirmOverwrite) {
                throw new \InvalidArgumentException('CONFIRM_OVERWRITE');
            }

            if ($conflicts !== [] && $confirmOverwrite) {
                ClassProgressAttachment::query()->whereIn('report_id', $conflicts)->delete();
                ClassProgressReport::query()->whereIn('id', $conflicts)->delete();
            }
        }

        $allowedIds = $this->queries->resolveAssignedTeacherIds($classSectionId);
        if ($allowedIds === []) {
            $allowedIds = [(int) $user->id];
        }

        return DB::transaction(function () use (
            $user,
            $anchor,
            $payload,
            $subjectSections,
            $classSectionId,
            $weekStart,
            $weekEnd,
            $schoolYear,
            $semester,
            $filesBySubject,
            $allowedIds,
            $originalWeekStart,
        ): Collection {
            $weeklyBatch = ClassProgressReport::query()
                ->whereIn('teacher_id', $allowedIds)
                ->where('class_section_id', $classSectionId)
                ->whereDate('week_start', $originalWeekStart)
                ->orderBy('subject')
                ->get();

            $reportMap = [];
            foreach ($weeklyBatch as $rep) {
                $subject = (string) ($rep->subject ?? '');
                if ($subject !== '') {
                    $reportMap[$subject] = $rep;
                }
            }

            $updated = collect();
            $flagsPresent = array_key_exists('flags', $payload);

            foreach ($subjectSections as $slug => $section) {
                $covered = trim((string) ($payload["covered_{$slug}"] ?? ''));
                if ($covered === '') {
                    continue;
                }

                $homework = trim((string) ($payload["homework_{$slug}"] ?? ''));
                $unitValues = (array) ($payload["unit_{$slug}"] ?? []);
                $chapterValues = (array) ($payload["chapter_{$slug}"] ?? []);
                $unitTitle = $this->rules->buildUnitChapterSummary($unitValues, $chapterValues);

                $subjectName = $section['db_subject'] ?? $section['label'] ?? $slug;
                $existing = $reportMap[$subjectName] ?? null;

                if ($unitTitle === null && $existing) {
                    $unitTitle = $existing->unit_title;
                }

                $data = [
                    'class_section_id' => $classSectionId,
                    'week_start' => $weekStart,
                    'week_end' => $weekEnd,
                    'subject' => $subjectName,
                    'unit_title' => $unitTitle,
                    'covered' => $covered,
                    'homework' => $homework !== '' ? $homework : null,
                ];

                if ($flagsPresent) {
                    $data['flags_json'] = $this->rules->normalizeFlags($payload['flags'] ?? null);
                }

                if (Schema::hasColumn('class_progress_reports', 'school_year')) {
                    $data['school_year'] = $schoolYear !== '' ? $schoolYear : null;
                }
                if (Schema::hasColumn('class_progress_reports', 'semester')) {
                    $data['semester'] = $semester !== '' ? $semester : null;
                }

                if ($existing) {
                    $existing->fill($data);
                    $existing->save();
                    $saved = $existing->fresh();
                } else {
                    $data['teacher_id'] = $user->id;
                    $data['status'] = $this->rules->defaultStatus();
                    $saved = ClassProgressReport::query()->create($data);
                }

                if (! $saved) {
                    continue;
                }

                $updated->push($saved);

                $attachments = $filesBySubject[$slug] ?? [];
                $stored = $this->attachments->storeAttachments((int) $saved->id, $attachments);
                if ($stored !== [] && ! $saved->attachment_path) {
                    $saved->update(['attachment_path' => $stored[0]['file_path'] ?? null]);
                }
            }

            if ($updated->isEmpty()) {
                throw new \InvalidArgumentException('Please provide progress for at least one subject.');
            }

            Log::info('Class progress weekly sync completed.', [
                'teacher_id' => $user->id,
                'anchor_id' => $anchor->id,
                'count' => $updated->count(),
            ]);

            return $updated;
        });
    }

    public function updateReport(ClassProgressReport $report, array $payload, array $attachments): ClassProgressReport
    {
        return DB::transaction(function () use ($report, $payload, $attachments) {
            $weekStart = $payload['week_start'] ?? $report->week_start?->format('Y-m-d');
            $weekEnd = $this->rules->ensureWeekEnd((string) $weekStart, $payload['week_end'] ?? null);
            if ($weekStart && $weekEnd && !$this->rules->isWeekEndValid((string) $weekStart, (string) $weekEnd)) {
                throw new \InvalidArgumentException('Week end must be the same as or after the week start.');
            }

            $data = [];
            if ($weekStart) {
                $data['week_start'] = $weekStart;
            }
            if ($weekEnd) {
                $data['week_end'] = $weekEnd;
            }
            if (array_key_exists('covered', $payload)) {
                $data['covered'] = $payload['covered'];
            }
            if (array_key_exists('homework', $payload)) {
                $data['homework'] = $payload['homework'];
            }
            if (array_key_exists('unit_title', $payload)) {
                $data['unit_title'] = $payload['unit_title'];
            }
            if (array_key_exists('status', $payload)) {
                $data['status'] = $payload['status'];
            }
            if (array_key_exists('support_needed', $payload)) {
                $data['support_needed'] = $payload['support_needed'];
            }
            if (array_key_exists('flags', $payload)) {
                $data['flags_json'] = $this->rules->normalizeFlags($payload['flags']);
            }

            if ($data !== []) {
                $report->fill($data);
                $report->save();
            }

            if ($attachments !== []) {
                $stored = $this->attachments->storeAttachments($report->id, $attachments);
                if ($stored !== []) {
                    $report->update(['attachment_path' => $stored[0]['file_path'] ?? $report->attachment_path]);
                }
            }

            return $report->fresh(['classSection', 'attachments']);
        });
    }

    public function deleteReport(ClassProgressReport $report): void
    {
        DB::transaction(function () use ($report) {
            $report->attachments()->delete();
            $report->delete();
        });
    }

    private function assertTeacherAssignment(User $user, int $classSectionId): void
    {
        if ($classSectionId <= 0) {
            throw new \InvalidArgumentException('No class assignment found for this report.');
        }

        if ($this->isAdmin($user)) {
            return;
        }

        $assigned = TeacherClass::query()
            ->where('teacher_id', $user->id)
            ->where('class_section_id', $classSectionId)
            ->exists();

        if (!$assigned) {
            throw new \InvalidArgumentException('No class assignment found for this report.');
        }
    }

    private function isAdmin(User $user): bool
    {
        $roles = $user->roles()
            ->pluck('roles.name')
            ->map(fn ($name) => strtolower((string) $name))
            ->toArray();

        foreach (['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'] as $role) {
            if (in_array($role, $roles, true)) {
                return true;
            }
        }

        return false;
    }
}
