<?php

namespace App\Http\Controllers;

use App\Models\MeetupBooth;
use App\Models\MeetupEvent;
use App\Models\MeetupSession;
use App\Services\MeetupService;
use App\Services\OrganiserNoticeService;
use App\Services\UsageService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

class MeetupAdminController extends Controller
{
    public function __construct(
        private MeetupService $meetup,
        private UsageService $usage,
        private OrganiserNoticeService $notices,
    ) {}

    public function showLogin(): View
    {
        return view('admin.login');
    }

    public function login(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'email' => 'required|email|max:255',
            'password' => 'required|string',
        ]);

        $result = $this->meetup->authenticateOrganiser($data['email'], $data['password']);

        if ($result['type'] === 'failed' || $result['events']->isEmpty()) {
            return back()->withInput()->with('error', 'Invalid email or password.');
        }

        if ($result['events']->count() === 1) {
            return $this->enterOrganiserSession($request, $result['events']->first()->id, $result['user']?->id);
        }

        $request->session()->put('meetup_organiser_pending_user_id', $result['user']?->id);
        $request->session()->put('meetup_organiser_pending_event_ids', $result['events']->pluck('id')->all());

        return redirect()->route('mexco.admin.select-event');
    }

    public function showSelectEvent(Request $request): View|RedirectResponse
    {
        $eventIds = $request->session()->get('meetup_organiser_pending_event_ids', []);
        if (empty($eventIds)) {
            return redirect()->route('mexco.admin.login');
        }

        $events = \App\Models\MeetupEvent::query()->whereIn('id', $eventIds)->orderBy('name')->get();

        return view('admin.select-event', compact('events'));
    }

    public function selectEvent(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'event_id' => 'required|string|exists:meetup_events,id',
        ]);

        $allowed = $request->session()->get('meetup_organiser_pending_event_ids', []);
        if (! in_array($data['event_id'], $allowed, true)) {
            return back()->with('error', 'You do not have access to that event.');
        }

        $userId = $request->session()->get('meetup_organiser_pending_user_id');
        $request->session()->forget(['meetup_organiser_pending_user_id', 'meetup_organiser_pending_event_ids']);

        return $this->enterOrganiserSession($request, $data['event_id'], $userId);
    }

    protected function enterOrganiserSession(Request $request, string $eventId, ?int $organiserUserId = null): RedirectResponse
    {
        $request->session()->put('meetup_admin_event', $eventId);
        $request->session()->put('meetup_admin_authenticated', true);
        if ($organiserUserId) {
            $request->session()->put('meetup_admin_organiser_user_id', $organiserUserId);
        }

        return redirect()->route('mexco.admin.dashboard');
    }

    public function logout(Request $request): RedirectResponse
    {
        $request->session()->forget([
            'meetup_admin_event',
            'meetup_admin_authenticated',
            'meetup_admin_organiser_user_id',
            'meetup_organiser_pending_user_id',
            'meetup_organiser_pending_event_ids',
        ]);

        return redirect()->route('mexco.admin.login')->with('success', 'Signed out.');
    }

    public function dashboard(Request $request): View
    {
        $event = $this->currentEvent($request);
        $stats = $this->meetup->dashboardStats($event->id);

        return view('admin.dashboard', [
            'event' => $event,
            'stats' => $stats,
            'usageSummary' => $this->usage->summary($event->id),
            'roleColors' => $this->meetup->roleColors(),
            'recentActivity' => $this->meetup->recentActivity($event->id),
        ]);
    }

    public function markNoticeRead(Request $request, int $notice): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $userId = $request->session()->get('meetup_admin_organiser_user_id');
        $this->notices->markRead($notice, $event, $userId ? (int) $userId : null);

        return back();
    }

    public function markAllNoticesRead(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $userId = $request->session()->get('meetup_admin_organiser_user_id');
        $this->notices->markAllRead($event, $userId ? (int) $userId : null);

        return back()->with('success', 'All notifications marked as read.');
    }

    public function participants(Request $request): View
    {
        $event = $this->currentEvent($request);
        $q = strtolower(trim($request->input('q', '')));
        $roleFilter = $request->input('role', '');
        $statusFilter = $request->input('status', '');

        $people = collect($this->meetup->listPeople($event->id))
            ->filter(function ($p) use ($q, $roleFilter, $statusFilter) {
                if ($roleFilter && strtoupper($p['role']) !== strtoupper($roleFilter)) {
                    return false;
                }
                if ($statusFilter === 'verified' && empty($p['verified'])) {
                    return false;
                }
                if ($statusFilter === 'pending' && ! empty($p['verified'])) {
                    return false;
                }
                if ($q === '') {
                    return true;
                }
                $ic = $this->meetup->digits($p['ic'] ?? '');

                return str_contains(strtolower($p['name']), $q)
                    || str_contains(strtolower($p['email']), $q)
                    || str_contains(strtolower($p['phone'] ?? ''), $q)
                    || ($ic && str_contains($ic, preg_replace('/\D/', '', $q)));
            })
            ->values();

        $roles = $event->roles->keyBy('name');

        return view('admin.participants.index', [
            'event' => $event,
            'people' => $people,
            'roles' => $event->roles,
            'roleColors' => $this->meetup->roleColors(),
            'q' => $request->input('q', ''),
            'roleFilter' => $roleFilter,
            'statusFilter' => $statusFilter,
            'totalCount' => count($this->meetup->listPeople($event->id)),
        ]);
    }

    public function createParticipant(Request $request): View
    {
        $event = $this->currentEvent($request);

        return view('admin.participants.form', [
            'event' => $event,
            'participant' => null,
            'roles' => $event->roles,
            'lookup' => session('participant_lookup'),
        ]);
    }

    public function lookupParticipant(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $data = $request->validate([
            'email' => 'required|email|max:255',
        ]);

        $email = strtolower($data['email']);
        $onThisEvent = $this->meetup->findParticipantByEmail($event->id, $email);
        if ($onThisEvent) {
            return redirect()
                ->route('mexco.admin.participants.create')
                ->withInput(['email' => $email])
                ->with('participant_lookup', [
                    'on_this_event' => true,
                    'email' => $email,
                    'name' => $onThisEvent['name'] ?? '',
                    'user_id' => $onThisEvent['user_id'] ?? null,
                ]);
        }

        $found = $this->meetup->findAccountProfileByEmail($email, $event->id);

        return redirect()
            ->route('mexco.admin.participants.create')
            ->withInput(['email' => $email])
            ->with('participant_lookup', $found ?: [
                'missing' => true,
                'email' => $email,
            ]);
    }

    public function editParticipant(Request $request, string $slug): View
    {
        $event = $this->currentEvent($request);
        $participant = $this->meetup->getParticipant($event->id, $slug);

        abort_if(! $participant, 404);

        return view('admin.participants.form', [
            'event' => $event,
            'participant' => $participant,
            'roles' => $event->roles,
            'lookup' => null,
        ]);
    }

    public function storeParticipant(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $data = $this->validatedParticipant($request);

        try {
            $result = $this->meetup->upsertParticipant($event->id, null, $data);
        } catch (ValidationException $e) {
            return back()->withInput()->withErrors($e->errors());
        }

        if (! ($result['ok'] ?? false)) {
            return back()->withInput()->with('error', $result['error'] ?? 'Could not save participant.');
        }

        $message = ! empty($result['linked'])
            ? $data['name'].' added to this event (linked to their existing Pass account).'
            : $data['name'].' added.';

        return redirect()->route('mexco.admin.participants')->with('success', $message);
    }

    public function updateParticipant(Request $request, string $slug): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $data = $this->validatedParticipant($request);

        $this->meetup->upsertParticipant($event->id, $slug, $data);

        return redirect()->route('mexco.admin.participants')->with('success', $data['name'].' updated.');
    }

    public function destroyParticipant(Request $request, string $slug): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $p = $this->meetup->getParticipant($event->id, $slug);
        $this->meetup->deleteParticipant($event->id, $slug);

        return redirect()->route('mexco.admin.participants')
            ->with('success', ($p['name'] ?? 'Participant').' removed.');
    }

    public function verifyParticipant(Request $request, string $slug): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $result = $this->meetup->verifyParticipant($event->id, $slug);

        return redirect()->route('mexco.admin.participants')
            ->with($result['ok'] ? 'success' : 'error', $result['msg']);
    }

    public function committeeAccounts(Request $request): View
    {
        $event = $this->currentEvent($request);
        $this->meetup->ensureAttendRoles($event->id);
        $event->load('roles');

        $q = strtolower(trim($request->input('q', '')));
        $people = collect($this->meetup->listCommitteeAccounts($event->id))
            ->filter(function ($p) use ($q) {
                if ($q === '') {
                    return true;
                }
                $ic = $this->meetup->digits($p['ic'] ?? '');

                return str_contains(strtolower($p['name'] ?? ''), $q)
                    || str_contains(strtolower($p['email'] ?? ''), $q)
                    || str_contains(strtolower($p['phone'] ?? ''), $q)
                    || ($ic && str_contains($ic, preg_replace('/\D/', '', $q)));
            })
            ->values();

        return view('admin.committee.index', [
            'event' => $event,
            'people' => $people,
            'roleColors' => $this->meetup->roleColors(),
            'q' => $request->input('q', ''),
            'attendUrl' => route('mexco.committee', ['switch' => 1]),
        ]);
    }

    public function createCommitteeAccount(Request $request): View
    {
        $event = $this->currentEvent($request);
        $this->meetup->ensureAttendRoles($event->id);

        return view('admin.committee.form', [
            'event' => $event,
            'account' => null,
            'found' => null,
            'attendUrl' => route('mexco.committee', ['switch' => 1]),
        ]);
    }

    public function lookupCommitteeAccount(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $this->meetup->ensureAttendRoles($event->id);

        $data = $request->validate([
            'email' => 'required|email|max:255',
        ]);

        $email = strtolower($data['email']);
        $found = $this->meetup->findParticipantByEmail($event->id, $email);

        if (! $found) {
            $elsewhere = $this->meetup->findAccountProfileByEmail($email, $event->id);
            if ($elsewhere) {
                $found = array_merge($elsewhere, [
                    'from_other_event' => true,
                ]);
            }
        }

        return redirect()
            ->route('mexco.admin.committee.create')
            ->withInput(['email' => $email])
            ->with('committee_lookup', $found ?: ['missing' => true, 'email' => $email]);
    }

    public function editCommitteeAccount(Request $request, string $slug): View
    {
        $event = $this->currentEvent($request);
        $this->meetup->ensureAttendRoles($event->id);
        $account = $this->meetup->getParticipant($event->id, $slug);

        abort_if(! $account, 404);
        abort_unless($this->meetup->hasCommitteeAccess($account), 404);

        return view('admin.committee.form', [
            'event' => $event,
            'account' => $account,
            'found' => null,
            'attendUrl' => route('mexco.committee', [
                'switch' => 1,
                'email' => $account['email'] ?? null,
            ]),
        ]);
    }

    public function storeCommitteeAccount(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $this->meetup->ensureAttendRoles($event->id);
        $data = $this->validatedCommitteeAccount($request, promoting: true);

        try {
            $result = $this->meetup->registerCommitteeAccount($event->id, $data);
        } catch (ValidationException $e) {
            return back()->withInput()->withErrors($e->errors());
        }

        if (! ($result['ok'] ?? false)) {
            return back()->withInput()->with('error', $result['error'] ?? 'Could not save committee account.');
        }

        $name = $result['participant']['name'] ?? $data['name'] ?? 'Account';
        $message = match (true) {
            ! empty($result['promoted']) => $name.' kept their existing role and was added as committee.',
            ! empty($result['already']) => $name.' already has committee access. Details updated.',
            default => $name.' can now sign in to MeXCo Attend.',
        };

        return redirect()->route('mexco.admin.committee')->with('success', $message);
    }

    public function updateCommitteeAccount(Request $request, string $slug): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $existing = $this->meetup->getParticipant($event->id, $slug);
        abort_if(! $existing, 404);
        abort_unless($this->meetup->hasCommitteeAccess($existing), 404);

        $data = $this->validatedCommitteeAccount($request);
        $this->meetup->upsertParticipant($event->id, $slug, [
            ...$data,
            'role' => $existing['role'] ?? 'VISITOR',
            'is_committee' => true,
        ]);

        return redirect()->route('mexco.admin.committee')
            ->with('success', ($data['name'] ?? $existing['name']).' updated.');
    }

    public function destroyCommitteeAccount(Request $request, string $slug): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $account = $this->meetup->getParticipant($event->id, $slug);
        abort_if(! $account, 404);
        abort_unless($this->meetup->hasCommitteeAccess($account), 404);

        $this->meetup->revokeCommitteeAccess($event->id, $slug);

        return redirect()->route('mexco.admin.committee')
            ->with('success', ($account['name'] ?? 'Account').' removed from committee. Their participant role was kept.');
    }

    public function exportParticipants(Request $request): StreamedResponse
    {
        $event = $this->currentEvent($request);
        $people = $this->meetup->listPeople($event->id);
        $booths = $event->booths;
        $roles = $event->roles->keyBy('name');

        $filename = 'meetup-participants-'.$event->id.'.csv';

        return response()->streamDownload(function () use ($people, $booths, $roles) {
            $out = fopen('php://output', 'w');
            $cols = $booths->pluck('code')->all();
            fputcsv($out, array_merge(
                ['name', 'ic', 'organization', 'email', 'phone', 'role', 'verified_at', 'sessions', 'stamps', 'certificate'],
                $cols
            ));
            foreach ($people as $p) {
                $stamps = $p['stamps'] ?? [];
                fputcsv($out, array_merge([
                    $p['name'],
                    $p['ic'],
                    $p['org'],
                    $p['email'],
                    $p['phone'],
                    $p['role'],
                    $p['verified'] ?? '',
                    count($p['sessions'] ?? []),
                    count($stamps),
                    $this->meetup->isEligible($p, $roles) ? 'eligible' : 'no',
                ], array_map(fn ($c) => $stamps[$c] ?? '', $cols)));
            }
            fclose($out);
        }, $filename, ['Content-Type' => 'text/csv']);
    }

    public function downloadParticipantTemplate(Request $request): StreamedResponse
    {
        $event = $this->currentEvent($request);
        $roles = $event->roles->pluck('name')->values();
        $sampleRole = $roles->first() ?: 'VISITOR';
        $filename = 'participant-import-template-'.$event->id.'.csv';

        return response()->streamDownload(function () use ($sampleRole, $roles) {
            $out = fopen('php://output', 'w');
            // Headers match the participant form / import columns
            fputcsv($out, ['name', 'email', 'organization', 'ic', 'phone', 'role']);
            fputcsv($out, [
                'Sample Participant',
                'sample@email.com',
                'Example Organisation',
                '900101015555',
                '60123456789',
                $sampleRole,
            ]);
            if ($roles->count() > 1) {
                fputcsv($out, [
                    'Another Participant',
                    'another@email.com',
                    'Example Organisation',
                    '880202025555',
                    '60198765432',
                    $roles->get(1),
                ]);
            }
            fclose($out);
        }, $filename, ['Content-Type' => 'text/csv']);
    }

    public function importParticipants(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);

        try {
            $request->validate(['csv_file' => 'required|file|mimes:csv,txt|max:2048']);
        } catch (ValidationException $e) {
            return redirect()
                ->route('mexco.admin.participants')
                ->with('error', collect($e->errors())->flatten()->first() ?: 'Please choose a valid CSV file (max 2MB).');
        }

        $file = $request->file('csv_file');
        $content = @file_get_contents($file->getRealPath() ?: '');
        if ($content === false || trim($content) === '') {
            return redirect()
                ->route('mexco.admin.participants')
                ->with('error', 'Could not read the CSV file. Please re-export it as CSV (UTF-8) and try again.');
        }

        try {
            $result = $this->meetup->importParticipantsFromCsv($event->id, $content);
        } catch (ValidationException $e) {
            $message = collect($e->errors())->flatten()->first()
                ?: 'Import blocked by event limits or validation.';

            return redirect()
                ->route('mexco.admin.participants')
                ->with('error', $message);
        } catch (\Throwable $e) {
            report($e);

            return redirect()
                ->route('mexco.admin.participants')
                ->with('error', 'Import failed unexpectedly. Please check the CSV format and try again.');
        }

        $added = (int) ($result['added'] ?? 0);
        $duplicates = $result['duplicates'] ?? [];
        $invalid = $result['invalid'] ?? [];
        $duplicateCount = count($duplicates);
        $invalidCount = count($invalid);

        $redirect = redirect()->route('mexco.admin.participants');

        if ($added > 0) {
            $redirect->with('success', $added.' participant'.($added === 1 ? '' : 's').' imported successfully.');
        }

        if ($duplicateCount > 0 || $invalidCount > 0) {
            $redirect->with('import_report', [
                'added' => $added,
                'duplicates' => $duplicates,
                'invalid' => $invalid,
            ]);
        }

        if ($added === 0 && $duplicateCount === 0 && $invalidCount === 0) {
            $redirect->with('error', 'No participant rows were found in the CSV file.');
        } elseif ($added === 0 && $invalidCount > 0 && $duplicateCount === 0) {
            $redirect->with(
                'error',
                $invalidCount.' row'.($invalidCount === 1 ? '' : 's').' could not be imported. See details below.'
            );
        } elseif ($added === 0 && $duplicateCount > 0 && $invalidCount === 0) {
            $redirect->with(
                'error',
                $duplicateCount.' row'.($duplicateCount === 1 ? '' : 's').' already exist (email + IC). New CSV data was ignored.'
            );
        } elseif ($added === 0 && $duplicateCount > 0 && $invalidCount > 0) {
            $redirect->with(
                'error',
                'No new participants added. '.$duplicateCount.' duplicate'.($duplicateCount === 1 ? '' : 's').' ignored, '.$invalidCount.' invalid.'
            );
        } elseif ($added > 0 && $duplicateCount > 0 && $invalidCount === 0) {
            $redirect->with(
                'warning',
                $duplicateCount.' existing participant'.($duplicateCount === 1 ? '' : 's').' skipped (same email + IC).'
            );
        } elseif ($added > 0 && $invalidCount > 0) {
            $redirect->with(
                'warning',
                $invalidCount.' row'.($invalidCount === 1 ? '' : 's').' had errors and were skipped. See details below.'
            );
        }

        return $redirect;
    }

    public function roles(Request $request): View
    {
        $event = $this->currentEvent($request);
        $people = $this->meetup->listPeople($event->id);
        $roleCounts = [];
        foreach ($people as $p) {
            $r = strtoupper($p['role'] ?? 'VISITOR');
            $roleCounts[$r] = ($roleCounts[$r] ?? 0) + 1;
        }

        return view('admin.roles.index', [
            'event' => $event,
            'roles' => $event->roles,
            'roleColors' => $this->meetup->roleColors(),
            'roleCounts' => $roleCounts,
        ]);
    }

    public function updateRoles(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $input = $request->input('roles', []);
        $roles = [];

        foreach ($event->roles as $role) {
            $cfg = $input[$role->name] ?? [];
            $sessions = trim($cfg['sessions'] ?? '');
            $roles[$role->name] = [
                'sessions' => $sessions === '' ? null : max(0, (int) $sessions),
                'stamps' => max(0, (int) ($cfg['stamps'] ?? 0)),
                'note' => $role->note,
            ];
        }

        $this->meetup->saveRoles($event->id, $roles);

        return back()->with('success', 'Roles saved.');
    }

    public function sessions(Request $request): View
    {
        $event = $this->currentEvent($request);
        $sessions = $event->sessions->sortBy([['day', 'asc'], ['time', 'asc']])->values();

        return view('admin.sessions.index', [
            'event' => $event,
            'sessions' => $sessions,
        ]);
    }

    public function sessionsTentative(Request $request): View
    {
        $event = $this->currentEvent($request);
        $sessionsByDay = $event->sessions
            ->filter(fn ($s) => $s->is_published)
            ->sortBy([['day', 'asc'], ['time', 'asc']])
            ->groupBy(fn ($s) => $s->day->format('Y-m-d'));

        return view('admin.sessions.tentative', compact('event', 'sessionsByDay'));
    }

    public function createSession(Request $request): View
    {
        $event = $this->currentEvent($request);

        return view('admin.sessions.form', [
            'event' => $event,
            'session' => null,
            'roles' => $event->roles,
            'suggestedKey' => $this->meetup->nextSessionKey($event->id),
            'timeParts' => $this->meetup->sessionTimeToParts(),
        ]);
    }

    public function storeSession(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $data = $this->validatedSession($request, $event->id);

        try {
            $this->meetup->createSession($event->id, $data);
        } catch (ValidationException $e) {
            return back()->withInput()->withErrors($e->errors());
        }

        return redirect()->route('mexco.admin.sessions')
            ->with('success', 'Session "'.$data['title'].'" created.');
    }

    public function editSession(Request $request, MeetupSession $session): View
    {
        $event = $this->currentEvent($request);
        abort_unless($session->event_id === $event->id, 404);

        return view('admin.sessions.form', [
            'event' => $event,
            'session' => $session,
            'roles' => $event->roles,
            'suggestedKey' => $session->session_key,
            'timeParts' => $this->meetup->sessionTimeToParts($session->time),
        ]);
    }

    public function updateSession(Request $request, MeetupSession $session): RedirectResponse
    {
        $event = $this->currentEvent($request);
        abort_unless($session->event_id === $event->id, 404);

        $data = $this->validatedSession($request, $event->id, $session->id);
        $this->meetup->updateSession($session, $data);

        return redirect()->route('mexco.admin.sessions')
            ->with('success', 'Session "'.$data['title'].'" updated.');
    }

    public function destroySession(Request $request, MeetupSession $session): RedirectResponse
    {
        $event = $this->currentEvent($request);
        abort_unless($session->event_id === $event->id, 404);

        $title = $session->title;
        $this->meetup->deleteSession($session);

        return redirect()->route('mexco.admin.sessions')
            ->with('success', 'Session "'.$title.'" deleted.');
    }

    public function toggleSessionPublish(Request $request, MeetupSession $session): RedirectResponse
    {
        $event = $this->currentEvent($request);
        abort_unless($session->event_id === $event->id, 404);

        $session->is_published = ! $session->is_published;
        $session->save();

        $label = $session->is_published ? 'published' : 'unpublished';

        return back()->with('success', 'Session "'.$session->title.'" is now '.$label.'.');
    }

    public function booths(Request $request): View
    {
        $event = $this->currentEvent($request);
        $event->load(['booths' => fn ($q) => $q->withCount('votes')]);
        $q = strtolower(trim($request->input('q', '')));
        $statusFilter = $request->input('status', '');
        $typeFilter = $request->input('type', '');

        $booths = $event->booths->filter(function ($b) use ($q, $statusFilter, $typeFilter) {
            if ($statusFilter && $b->status !== $statusFilter) {
                return false;
            }
            if ($typeFilter && $b->booth_type !== $typeFilter) {
                return false;
            }
            if ($q === '') {
                return true;
            }

            return str_contains(strtolower($b->name), $q)
                || str_contains(strtolower($b->code), $q)
                || str_contains(strtolower($b->location ?? ''), $q);
        });

        return view('admin.booths.index', [
            'event' => $event,
            'booths' => $booths->values(),
            'q' => $request->input('q', ''),
            'statusFilter' => $statusFilter,
            'typeFilter' => $typeFilter,
            'totalCount' => $event->booths->count(),
        ]);
    }

    public function createBooth(Request $request): View
    {
        return view('admin.booths.form', [
            'event' => $this->currentEvent($request),
            'booth' => null,
        ]);
    }

    public function storeBooth(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $data = $this->validateBooth($request, $event->id);
        $data = $this->boothToggleFields($request, $data);

        if ($request->hasFile('logo')) {
            $data['logo_path'] = $request->file('logo')->store('booth-logos', 'public');
        }

        try {
            $booth = $this->meetup->createBooth($event->id, $data);
        } catch (ValidationException $e) {
            return back()->withInput()->withErrors($e->errors());
        }

        return redirect()->route('mexco.admin.booths.show', $booth)
            ->with('success', 'Booth registered. QR code is ready.');
    }

    public function showBooth(Request $request, MeetupBooth $booth): View
    {
        $event = $this->currentEvent($request);
        abort_unless($booth->event_id === $event->id, 404);
        $booth->loadCount('votes');

        return view('admin.booths.show', compact('event', 'booth'));
    }

    public function toggleBoothStatus(Request $request, MeetupBooth $booth): RedirectResponse
    {
        $event = $this->currentEvent($request);
        abort_unless($booth->event_id === $event->id, 404);

        $booth->status = $booth->status === 'active' ? 'inactive' : 'active';
        $booth->save();

        return back()->with('success', 'Booth "'.$booth->name.'" is now '.$booth->status.'.');
    }

    public function toggleBoothVoting(Request $request, MeetupBooth $booth): RedirectResponse
    {
        $event = $this->currentEvent($request);
        abort_unless($booth->event_id === $event->id, 404);
        abort_unless(app(\App\Services\EntitlementService::class)->allows($event->id, 'booth_voting'), 403, 'Voting is not allowed for this event. Ask a super admin to enable it.');

        $booth->voting_enabled = ! $booth->voting_enabled;
        $booth->save();

        return back()->with(
            'success',
            'Voting for "'.$booth->name.'" is now '.($booth->voting_enabled ? 'on' : 'off').'.'
        );
    }

    public function toggleAllBoothVoting(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        abort_unless(app(\App\Services\EntitlementService::class)->allows($event->id, 'booth_voting'), 403, 'Voting is not allowed for this event. Ask a super admin to enable it.');

        $booths = MeetupBooth::query()->where('event_id', $event->id);
        $total = (clone $booths)->count();
        abort_if($total === 0, 404, 'No booths to update.');

        $allOn = (clone $booths)->where('voting_enabled', false)->count() === 0;
        $enable = ! $allOn;

        MeetupBooth::query()
            ->where('event_id', $event->id)
            ->update(['voting_enabled' => $enable]);

        return back()->with(
            'success',
            $enable
                ? 'Voting activated for all booths. You can still turn it off per booth.'
                : 'Voting deactivated for all booths.'
        );
    }

    public function printBooth(Request $request, MeetupBooth $booth): View
    {
        $event = $this->currentEvent($request);
        abort_unless($booth->event_id === $event->id, 404);

        return view('admin.booths.print', compact('booth'));
    }

    public function printAllBooths(Request $request): View
    {
        $event = $this->currentEvent($request);
        $booths = $event->booths;

        return view('admin.booths.print-sheet', compact('event', 'booths'));
    }

    public function editBooth(Request $request, MeetupBooth $booth): View
    {
        $event = $this->currentEvent($request);
        abort_unless($booth->event_id === $event->id, 404);

        return view('admin.booths.form', compact('event', 'booth'));
    }

    public function updateBoothRecord(Request $request, MeetupBooth $booth): RedirectResponse
    {
        $event = $this->currentEvent($request);
        abort_unless($booth->event_id === $event->id, 404);

        $data = $this->validateBooth($request, $event->id, $booth->id);
        $data = $this->boothToggleFields($request, $data);

        if ($request->hasFile('logo')) {
            $data['logo_path'] = $request->file('logo')->store('booth-logos', 'public');
        }

        $this->meetup->updateBoothRecord($booth, $data);

        return redirect()->route('mexco.admin.booths.show', $booth)
            ->with('success', 'Booth updated.');
    }

    public function destroyBooth(Request $request, MeetupBooth $booth): RedirectResponse
    {
        $event = $this->currentEvent($request);
        abort_unless($booth->event_id === $event->id, 404);

        $name = $booth->name;
        $this->meetup->deleteBoothRecord($booth);

        return redirect()->route('mexco.admin.booths')
            ->with('success', 'Booth "'.$name.'" deleted.');
    }

    public function exportBooths(Request $request): StreamedResponse
    {
        $event = $this->currentEvent($request);
        $filename = 'meetup-booths-'.$event->id.'.csv';

        return response()->streamDownload(function () use ($event) {
            $out = fopen('php://output', 'w');
            fputcsv($out, ['Code', 'Name', 'Mark', 'Type', 'Location', 'Status']);
            foreach ($event->booths as $b) {
                fputcsv($out, [
                    $b->code,
                    $b->name,
                    $b->mark,
                    $this->meetup->boothTypeLabel($b->booth_type),
                    $b->location ?? '',
                    $b->status,
                ]);
            }
            fclose($out);
        }, $filename, ['Content-Type' => 'text/csv']);
    }

    protected function validateBooth(Request $request, string $eventId, ?int $boothId = null): array
    {
        $uniqueCode = 'unique:meetup_booths,code,NULL,id,event_id,'.$eventId;
        if ($boothId) {
            $uniqueCode = 'unique:meetup_booths,code,'.$boothId.',id,event_id,'.$eventId;
        }

        return $request->validate([
            'code' => ['required', 'string', 'max:16', 'regex:/^[A-Za-z0-9_-]+$/', $uniqueCode],
            'name' => 'required|string|max:255',
            'mark' => 'required|string|max:2',
            'location' => 'nullable|string|max:255',
            'status' => 'required|in:active,inactive',
            'booth_type' => 'nullable|in:exhibitor,sponsor,info_desk',
            'description' => 'nullable|string|max:2000',
            'notes' => 'nullable|string|max:2000',
            'show_in_map' => 'nullable|boolean',
            'allow_checkin' => 'nullable|boolean',
            'require_approval' => 'nullable|boolean',
            'visible_public' => 'nullable|boolean',
            'voting_enabled' => 'nullable|boolean',
            'logo' => 'nullable|image|max:2048',
        ]);
    }

    protected function boothToggleFields(Request $request, array $data): array
    {
        $data = array_merge($data, [
            'show_in_map' => $request->boolean('show_in_map'),
            'allow_checkin' => $request->boolean('allow_checkin'),
            'require_approval' => $request->boolean('require_approval'),
            'visible_public' => $request->boolean('visible_public'),
        ]);

        $event = $this->currentEvent($request);
        $data['voting_enabled'] = app(\App\Services\EntitlementService::class)->allows($event->id, 'booth_voting') && $request->boolean('voting_enabled');

        return $data;
    }

    public function updateBooths(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $booths = $this->meetup->parseBoothsText($request->input('booths_text', ''));

        if (empty($booths)) {
            return back()->with('error', 'Add at least one booth.');
        }

        $this->meetup->saveBooths($event->id, $booths);

        return back()->with('success', 'Booths saved.');
    }

    public function certificates(Request $request): View
    {
        $event = $this->currentEvent($request);
        $roles = $event->roles->keyBy('name');
        $eligible = collect($this->meetup->listPeople($event->id))
            ->filter(fn ($p) => $this->meetup->isEligible($p, $roles));
        $tab = $request->input('tab', 'templates');

        return view('admin.certificates.index', [
            'event' => $event,
            'eligible' => $eligible,
            'roleColors' => $this->meetup->roleColors(),
            'tab' => $tab,
            'templates' => $this->meetup->certificateTemplatesForEvent($event),
        ]);
    }

    public function editCertificateTemplate(Request $request, string $key): View
    {
        $event = $this->currentEvent($request);
        $template = $this->meetup->certificateTemplate($event, $key);

        if (! $template) {
            abort(404);
        }

        return view('admin.certificates.form', [
            'event' => $event,
            'template' => $template,
            'roleNames' => collect($event->roles->pluck('name')->all())
                ->merge($template['roles'])
                ->map(fn ($r) => strtoupper(trim((string) $r)))
                ->unique()
                ->sort()
                ->values()
                ->all(),
        ]);
    }

    public function updateCertificateTemplate(Request $request, string $key): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $existing = $this->meetup->certificateTemplate($event, $key);

        if (! $existing) {
            abort(404);
        }

        $data = $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'nullable|string|max:500',
            'title' => 'required|string|max:255',
            'lead' => 'required|string|max:255',
            'body' => 'required|string|max:255',
            'roles' => 'required|array|min:1',
            'roles.*' => 'required|string|max:100',
            'background' => 'nullable|image|mimes:jpeg,jpg,png,webp|max:5120',
            'remove_background' => 'nullable|boolean',
        ]);

        $backgroundPath = $existing['background_path'] ?? null;

        if ($request->boolean('remove_background') && $backgroundPath) {
            Storage::disk('public')->delete($backgroundPath);
            $backgroundPath = null;
        }

        if ($request->hasFile('background')) {
            if ($backgroundPath) {
                Storage::disk('public')->delete($backgroundPath);
            }
            $backgroundPath = $request->file('background')
                ->store('certificate-backgrounds/'.$event->id, 'public');
        }

        $data['background_path'] = $backgroundPath;
        unset($data['background'], $data['remove_background']);

        $this->meetup->updateCertificateTemplate($event->id, $key, $data);

        return redirect()->route('mexco.admin.certificates', ['tab' => 'templates'])
            ->with('success', 'Certificate template updated.');
    }

    public function settings(Request $request): View|RedirectResponse
    {
        $event = $this->currentEvent($request);
        $section = $request->input('section', 'general');

        if (! in_array($section, ['general', 'integrations', 'security'], true)) {
            return redirect()->route('mexco.admin.settings', ['section' => 'general']);
        }

        return view('admin.settings.index', [
            'event' => $event,
            'section' => $section,
        ]);
    }

    public function updateSettings(Request $request): RedirectResponse
    {
        $event = $this->currentEvent($request);
        $section = $request->input('section', 'general');

        $eventRecord = MeetupEvent::query()->findOrFail($event->id);

        if ($section === 'general') {
            $data = $request->validate([
                'event_name' => 'required|string|max:255',
                'short_name' => 'required|string|max:100',
                'banner' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:4096',
                'remove_banner' => 'nullable|boolean',
                'floor_plan' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:6144',
                'remove_floor_plan' => 'nullable|boolean',
            ]);
            $this->meetup->saveSettings($event->id, [
                'eventName' => $data['event_name'],
                'shortName' => $data['short_name'],
                'signer' => $eventRecord->signer,
                'signerTitle' => $eventRecord->signer_title,
                'mailEndpoint' => $eventRecord->mail_endpoint,
            ]);
            $this->meetup->updateEventBanner(
                $event->id,
                $request->file('banner'),
                $request->boolean('remove_banner')
            );
            if (app(\App\Services\EntitlementService::class)->allows($eventRecord->id, 'floor_plan')) {
                $this->meetup->updateEventFloorPlan(
                    $event->id,
                    $request->file('floor_plan'),
                    $request->boolean('remove_floor_plan')
                );
            }
        } elseif ($section === 'integrations') {
            $data = $request->validate(['mail_endpoint' => 'nullable|string|max:500']);
            $this->meetup->saveSettings($event->id, [
                'signer' => $eventRecord->signer,
                'signerTitle' => $eventRecord->signer_title,
                'mailEndpoint' => $data['mail_endpoint'] ?? '',
            ]);
        } elseif ($section === 'security') {
            $data = $request->validate([
                'password' => 'nullable|string|min:6|confirmed',
            ]);

            if (! empty($data['password'])) {
                $this->meetup->saveSettings($event->id, [
                    'signer' => $eventRecord->signer,
                    'signerTitle' => $eventRecord->signer_title,
                    'mailEndpoint' => $eventRecord->mail_endpoint,
                    'organiserPassword' => $data['password'],
                ]);
            }
        } else {
            $section = 'general';
        }

        return redirect()->route('mexco.admin.settings', ['section' => $section])
            ->with('success', 'Settings saved.');
    }

    protected function currentEvent(Request $request): MeetupEvent
    {
        $eventId = $request->session()->get('meetup_admin_event');

        return $this->meetup->getEvent($eventId);
    }

    protected function validatedParticipant(Request $request): array
    {
        return $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email|max:255',
            'org' => 'nullable|string|max:255',
            'ic' => 'nullable|string|max:20',
            'phone' => 'nullable|string|max:20',
            'role' => 'required|string|max:50',
        ]);
    }

    protected function validatedCommitteeAccount(Request $request, bool $promoting = false): array
    {
        $existing = null;
        if ($promoting && $request->filled('email')) {
            $event = $this->currentEvent($request);
            $existing = $this->meetup->findParticipantByEmail($event->id, (string) $request->input('email'));
        }

        $hasExistingIc = $existing && strlen($this->meetup->digits($existing['ic'] ?? '')) >= 6;

        $data = $request->validate([
            'name' => ($existing ? 'nullable' : 'required').'|string|max:255',
            'email' => 'required|email|max:255',
            'org' => 'nullable|string|max:255',
            'ic' => [
                $hasExistingIc ? 'nullable' : 'required',
                'string',
                'max:20',
                function ($attribute, $value, $fail) use ($hasExistingIc) {
                    $digits = preg_replace('/\D/', '', (string) $value) ?? '';
                    if ($digits === '' && $hasExistingIc) {
                        return;
                    }
                    if (strlen($digits) < 6) {
                        $fail('IC / ID must contain at least 6 digits for Attend login.');
                    }
                },
            ],
            'phone' => 'nullable|string|max:20',
        ]);

        if ($existing) {
            $data['name'] = trim((string) ($data['name'] ?? '')) ?: ($existing['name'] ?? 'Committee');
            if (! filled($data['ic'] ?? null)) {
                $data['ic'] = $existing['ic'] ?? '';
            }
            if (! array_key_exists('org', $data) || $data['org'] === null) {
                $data['org'] = $existing['org'] ?? '';
            }
            if (! array_key_exists('phone', $data) || $data['phone'] === null) {
                $data['phone'] = $existing['phone'] ?? '';
            }
        }

        return $data;
    }

    protected function validatedSession(Request $request, string $eventId, ?int $sessionId = null): array
    {
        $uniqueKey = 'unique:meetup_sessions,session_key,NULL,id,event_id,'.$eventId;
        if ($sessionId) {
            $uniqueKey = 'unique:meetup_sessions,session_key,'.$sessionId.',id,event_id,'.$eventId;
        }

        $data = $request->validate([
            'session_key' => ['required', 'string', 'max:20', $uniqueKey],
            'day' => 'required|date',
            'start_hour' => 'required|integer|min:1|max:12',
            'start_minute' => 'required|integer|in:0,15,30,45',
            'start_ampm' => 'required|in:AM,PM',
            'end_hour' => 'required|integer|min:1|max:12',
            'end_minute' => 'required|integer|in:0,15,30,45',
            'end_ampm' => 'required|in:AM,PM',
            'title' => 'required|string|max:255',
            'speaker' => 'nullable|string|max:255',
            'venue' => 'nullable|string|max:255',
            'allowed_roles' => 'nullable|array',
            'allowed_roles.*' => 'string|max:50',
            'is_parallel' => 'nullable|boolean',
        ]);

        $data['allowed_roles'] = array_values(array_filter($data['allowed_roles'] ?? []));
        $data['is_parallel'] = $request->boolean('is_parallel');

        $data['time'] = $this->meetup->buildSessionTimeFromParts(
            (int) $data['start_hour'],
            (int) $data['start_minute'],
            $data['start_ampm'],
            (int) $data['end_hour'],
            (int) $data['end_minute'],
            $data['end_ampm'],
        );

        if (! $data['time']) {
            throw ValidationException::withMessages([
                'end_hour' => 'End time must be after start time.',
            ]);
        }

        if (! $data['is_parallel']) {
            $overlap = $this->meetup->overlappingSessionOnDay($eventId, $data['day'], $data['time'], $sessionId);
            if ($overlap) {
                throw ValidationException::withMessages([
                    'start_hour' => 'This time overlaps with "'.$overlap->title.'" ('.$overlap->time.'). Check Parallel session to allow overlapping times.',
                ]);
            }
        }

        return $data;
    }
}
