<?php

namespace App\Livewire\Attendance;

use App\Domain\Attendance\Enums\AttendanceStatus;
use App\Domain\Attendance\Models\AttendanceRecord;
use App\Domain\Attendance\Services\AttendanceGenerationService;
use App\Domain\Attendance\Services\AttendanceMarkingService;
use App\Domain\Financial\Enums\InvoiceStatus;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingSession;
use App\Models\User;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Collection;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('تسجيل الحضور')]
class TakeAttendance extends Component
{
    /**
     * The four statuses a trainer can assign from this screen, in display order.
     * Everything else (no_show, partial, ...) is read-only here.
     */
    private const ACTION_STATUSES = ['present', 'absent', 'late', 'excused'];

    /** Order the recorded groups appear in inside the collapsed section. */
    private const RECORDED_GROUP_ORDER = ['present', 'late', 'absent', 'excused'];

    public TrainingSession $session;

    public string $sessionNotes = '';

    /** @var array<int, string> Per-record notes keyed by record id */
    public array $recordNotes = [];

    /** Whether the "already recorded" section is expanded. */
    public bool $showRecorded = false;

    /** Details of the last mark, shown as a confirmation toast. */
    public ?array $lastMarked = null;

    /** Increments on every mark so the toast re-mounts (and re-animates) each time. */
    public int $markSeq = 0;

    public function mount(TrainingSession $session, AttendanceGenerationService $generationService): void
    {
        $this->authorize('attendance.mark');

        // Read the session back through the model instead of using the injected
        // one. Implicit route-model binding happens in SubstituteBindings, which
        // sits inside the web group and therefore runs *before* the appended
        // ResolveBranchContext turns enforcement on — so the bound instance has
        // not passed through BranchScope. Re-reading it means another branch's
        // session 404s here rather than rendering its full roster and accepting
        // marks against it. In an unscoped context (console, portal, all
        // branches) this returns the same row.
        $this->session = TrainingSession::with('group')->findOrFail($session->getKey());
        $this->sessionNotes = $this->session->notes ?? '';

        $hasRecords = AttendanceRecord::where('training_session_id', $this->session->id)->exists();
        if (!$hasRecords) {
            $generationService->generateForSession($this->session);
        }
    }

    public function saveNotes(): void
    {
        $this->session->update(['notes' => $this->sessionNotes]);
        session()->flash('notes_saved', __('تم حفظ ملاحظات الجلسة'));
    }

    public function saveRecordNote(int $recordId): void
    {
        $record = $this->findSessionRecord($recordId);
        if (!$record) {
            return;
        }

        $record->update(['notes' => $this->recordNotes[$recordId] ?? '']);
        session()->flash("record_note_saved_{$recordId}", __('تم حفظ الملاحظة'));
    }

    public function markAs(int $recordId, string $status, AttendanceMarkingService $service): void
    {
        $record = $this->findSessionRecord($recordId);

        if (!$record || !$this->canMarkRecord($record)) {
            return;
        }

        if (!in_array($status, self::ACTION_STATUSES, true)) {
            return;
        }

        $statusEnum = AttendanceStatus::from($status);

        try {
            $service->markStatus($record, $statusEnum, auth()->user());
        } catch (DomainException $e) {
            $this->addError('mark', $e->getMessage());

            return;
        }

        $this->confirmMark($record, $statusEnum);
    }

    public function markPresent(int $recordId, AttendanceMarkingService $service): void
    {
        $record = $this->findSessionRecord($recordId);

        if (!$record || !$this->canMarkRecord($record)) {
            return;
        }

        try {
            $updated = $service->markPresent($record, auth()->user());
        } catch (DomainException $e) {
            $this->addError('mark', $e->getMessage());

            return;
        }

        $this->confirmMark($record, $updated->status);
    }

    public function markAllPresent(AttendanceMarkingService $service): void
    {
        $records = AttendanceRecord::where('training_session_id', $this->session->id)
            ->where('status', AttendanceStatus::Expected)
            ->get();

        $marked = 0;
        $blocked = 0;

        foreach ($records as $record) {
            if (!$this->canMarkRecord($record)) {
                continue;
            }

            try {
                $service->markPresent($record, auth()->user());
                $marked++;
            } catch (DomainException) {
                $blocked++;
            }
        }

        if ($blocked > 0) {
            $this->addError('mark', __('تعذر تسجيل :count لاعب — الشهادة الطبية مطلوبة أو منتهية', ['count' => $blocked]));
        }

        if ($marked > 0) {
            $this->markSeq++;
            $this->lastMarked = [
                'name' => __(':count لاعب', ['count' => $marked]),
                'label' => AttendanceStatus::Present->label(),
                'color' => AttendanceStatus::Present->color(),
            ];
        }
    }

    public function toggleRecorded(): void
    {
        $this->showRecorded = !$this->showRecorded;
    }

    public function dismissConfirmation(): void
    {
        $this->lastMarked = null;
    }

    private function confirmMark(AttendanceRecord $record, AttendanceStatus $status): void
    {
        $this->markSeq++;
        $this->lastMarked = [
            'name' => $this->subjectName($record),
            'label' => $status->label(),
            'color' => $status->color(),
        ];
    }

    private function findSessionRecord(int $recordId): ?AttendanceRecord
    {
        return AttendanceRecord::where('training_session_id', $this->session->id)
            ->where('id', $recordId)
            ->first();
    }

    private function subjectName(AttendanceRecord $record): string
    {
        if ($record->subject_type === Participant::class) {
            return $record->subject?->person?->name_ar ?? '-';
        }

        return $record->subject?->name ?? '-';
    }

    private function canMarkRecord(AttendanceRecord $record): bool
    {
        $user = auth()->user();

        if ($record->subject_type === Participant::class) {
            return true;
        }

        if ($record->subject_type === User::class) {
            if ($this->canManageTrainers()) {
                return true;
            }
            return $record->subject_id === $user->id;
        }

        return true;
    }

    private function canManageTrainers(): bool
    {
        $user = auth()->user();

        if ($user->is_super_admin) {
            return true;
        }

        $roleLevel = $user->roles->max('level') ?? 0;

        return $roleLevel >= 60;
    }

    /**
     * Stable, status-independent ordering: by name, then by id as a tie-break.
     * Marking a player must never move anybody in the list.
     */
    private function sortByName(Collection $records): Collection
    {
        return $records
            ->sortBy(fn (AttendanceRecord $record) => $this->subjectName($record)
                . '#' . str_pad((string) $record->id, 12, '0', STR_PAD_LEFT))
            ->values();
    }

    public function render()
    {
        $records = AttendanceRecord::where('training_session_id', $this->session->id)
            ->with(['subject' => fn (MorphTo $morphTo) => $morphTo->morphWith([
                Participant::class => ['person'],
            ])])
            ->orderBy('id')
            ->get();

        $trainerRecords = $this->sortByName($records->where('subject_type', User::class));
        $participantRecords = $this->sortByName($records->where('subject_type', Participant::class));

        // The working list holds only players nobody has decided on yet.
        // Once marked, a player leaves it and lands in the recorded section.
        $pendingParticipants = $participantRecords
            ->where('status', AttendanceStatus::Expected)
            ->values();

        $recordedParticipants = $participantRecords
            ->filter(fn (AttendanceRecord $record) => $record->status !== AttendanceStatus::Expected)
            ->values();

        $recordedGroups = $recordedParticipants
            ->groupBy(fn (AttendanceRecord $record) => $record->status->value)
            ->sortBy(function ($group, $statusValue) {
                $position = array_search($statusValue, self::RECORDED_GROUP_ORDER, true);

                return $position === false ? count(self::RECORDED_GROUP_ORDER) : $position;
            });

        // Populate per-record notes from DB (only on first load or if not yet set)
        foreach ($records as $record) {
            if (!array_key_exists($record->id, $this->recordNotes)) {
                $this->recordNotes[$record->id] = $record->notes ?? '';
            }
        }

        // Detect unpaid/unrenewed participants for red highlight
        $participantIds = $participantRecords
            ->pluck('subject_id')
            ->unique()
            ->toArray();

        $notPaidParticipantIds = [];
        if ($participantIds) {
            $unpaidInvoiceIds = Invoice::where('billable_type', Participant::class)
                ->whereIn('billable_id', $participantIds)
                ->whereIn('status', [InvoiceStatus::Sent, InvoiceStatus::Overdue, InvoiceStatus::PartiallyPaid])
                ->where('due_amount', '>', 0)
                ->whereNull('deleted_at')
                ->pluck('billable_id')
                ->unique()
                ->toArray();

            $overdueEnrollmentIds = Enrollment::whereIn('participant_id', $participantIds)
                ->where('status', 'active')
                ->where('training_group_id', $this->session->training_group_id)
                ->whereNotNull('next_billing_date')
                ->where('next_billing_date', '<', now()->toDateString())
                ->pluck('participant_id')
                ->unique()
                ->toArray();

            $notPaidParticipantIds = array_unique(array_merge($unpaidInvoiceIds, $overdueEnrollmentIds));
        }

        $summary = [
            'total' => $records->count(),
            'present' => $records->where('status', AttendanceStatus::Present)->count(),
            'late' => $records->where('status', AttendanceStatus::Late)->count(),
            'absent' => $records->where('status', AttendanceStatus::Absent)->count(),
            'expected' => $records->where('status', AttendanceStatus::Expected)->count(),
            'excused' => $records->where('status', AttendanceStatus::Excused)->count(),
        ];

        $participantsTotal = $participantRecords->count();
        $participantsDone = $participantsTotal - $pendingParticipants->count();

        $progress = [
            'total' => $participantsTotal,
            'done' => $participantsDone,
            'pending' => $pendingParticipants->count(),
            'percent' => $participantsTotal > 0
                ? (int) round(($participantsDone / $participantsTotal) * 100)
                : 0,
        ];

        return view('livewire.attendance.take-attendance', [
            'trainerRecords' => $trainerRecords,
            'participantRecords' => $participantRecords,
            'pendingParticipants' => $pendingParticipants,
            'recordedParticipants' => $recordedParticipants,
            'recordedGroups' => $recordedGroups,
            'summary' => $summary,
            'progress' => $progress,
            'notPaidParticipantIds' => $notPaidParticipantIds,
            'canManageTrainers' => $this->canManageTrainers(),
            'currentUserId' => auth()->id(),
        ]);
    }
}
