<?php

declare(strict_types=1);

namespace App\Modules\Guest;

use App\Core\BaseController;
use App\Core\Request;
use App\Core\Session;
use App\Modules\Operation\OperationModel;

/**
 * GuestController — HTTP Handler for Guest List Workspace and CRUD.
 *
 * Implements listing, search, filtering, manual CRUD validation, XLSX validation report
 * previews, final import approval, and downloadable templates.
 *
 * @package Inveetaire
 * @ref     RC-012
 */
class GuestController extends BaseController
{
    protected string $layout = 'couple';
    private GuestService $service;
    private GuestModel $model;
    private OperationModel $operationModel;

    public function __construct()
    {
        // Dynamically set layout based on the active route prefix role snapshot
        $role = Session::get('role_snapshot') === 'crew' ? 'crew' : 'couple';
        $this->layout = $role;

        parent::__construct();
        $this->service = new GuestService();
        $this->model = new GuestModel();
        $this->operationModel = new OperationModel();
    }

    /**
     * Display the Guest Management Workspace dashboard table.
     * Route: GET /app/couple/guests
     *
     * @param Request $request
     * @return void
     */
    public function index(Request $request): void
    {
        if ($request->get('ajax') === '1') {
            $this->layout = 'ajax';
        }
        $workspaceId = (int) Session::get('active_workspace_id');

        // Ensure pilot events exist and guests are migrated
        $this->operationModel->ensurePilotEventsExist($workspaceId);
        $this->operationModel->migrateExistingGuestsEvents($workspaceId);

        // Fetch all active events for workspace tabs
        $db = \App\Core\Database::getConnection();
        $stmt = $db->prepare('SELECT id, event_name FROM wedding_events WHERE workspace_id = :workspace_id AND is_deleted = 0 ORDER BY sort_order ASC');
        $stmt->execute(['workspace_id' => $workspaceId]);
        $workspaceEvents = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        // Determine active event tab (0 = All Guests)
        $activeEventId = (int) $request->get('event_id', 0);
        $validEventIds = array_column($workspaceEvents, 'id');
        if ($activeEventId !== 0 && !in_array($activeEventId, $validEventIds, true)) {
            $activeEventId = 0;
        }

        // 1. Gather filters
        $filters = [
            'search'     => trim((string) $request->get('search')),
            'group'      => trim((string) $request->get('group')),
            'rsvp'       => trim((string) $request->get('rsvp')),
            'attendance' => trim((string) $request->get('attendance'))
        ];

        // 2. Pagination variables
        $page = max(1, (int) $request->get('page', 1));
        $limit = 10;
        $offset = ($page - 1) * $limit;

        // 3. Fetch data from model (All Guests if event_id = 0, otherwise per-event)
        if ($activeEventId === 0) {
            $guests = $this->model->getGuests($workspaceId, $filters, $limit, $offset);
            $totalGuests = $this->model->getGuestsCount($workspaceId, $filters);
            $overallStats = $this->operationModel->getAttendanceStats($workspaceId);
            $stats = [
                'expected_guests'  => $overallStats['total_guests'] ?? 0,
                'expected_pax'     => $overallStats['total_invited_pax'] ?? 0,
                'pending_count'    => $overallStats['rsvp_pending_count'] ?? 0,
                'checked_in_count' => $overallStats['checked_in_count'] ?? 0,
            ];
        } else {
            $guests = $this->model->getGuestsForEvent($workspaceId, $activeEventId, $filters, $limit, $offset);
            $totalGuests = $this->model->getGuestsCountForEvent($workspaceId, $activeEventId, $filters);
            $stats = $this->operationModel->getEventAttendanceStats($workspaceId, $activeEventId);
        }

        $totalPages = (int) ceil($totalGuests / $limit);
        
        $categories = $this->model->getDistinctGroups($workspaceId);
        $attendanceMatrix = $this->model->getAttendanceMatrixForWorkspace($workspaceId);

        $this->render('guest/list', [
            'title'             => 'Guest List',
            'guests'            => $guests,
            'filters'           => $filters,
            'categories'        => $categories,
            'stats'             => $stats,
            'page'              => $page,
            'total_pages'       => $totalPages,
            'total_guests'      => $totalGuests,
            'workspace_events'  => $workspaceEvents,
            'active_event_id'   => $activeEventId,
            'attendance_matrix' => $attendanceMatrix
        ]);
    }

    /**
     * Show manual Add Guest form view.
     * Route: GET /app/couple/guests/create
     *
     * @param Request $request
     * @return void
     */
    public function create(Request $request): void
    {
        $workspaceId = (int) Session::get('active_workspace_id');
        
        // Fetch workspace events and event sessions
        $db = \App\Core\Database::getConnection();
        $stmt = $db->prepare('SELECT id, event_name FROM wedding_events WHERE workspace_id = :workspace_id AND is_deleted = 0 ORDER BY sort_order ASC');
        $stmt->execute(['workspace_id' => $workspaceId]);
        $workspaceEvents = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        $eventSessionModel = new \App\Modules\Invitation\EventSessionModel();
        $eventSessionModel->ensureDefaultSessions($workspaceId);
        $eventSessions = $eventSessionModel->getSessionsGroupedByEvent($workspaceId);

        $this->render('guest/create', [
            'title'            => 'Add New Guest',
            'errors'           => Session::get('errors') ?? [],
            'old'              => Session::get('old_input') ?? [],
            'workspace_events' => $workspaceEvents,
            'event_sessions'   => $eventSessions
        ]);

        Session::set('errors', null);
        Session::set('old_input', null);
    }

    /**
     * Process manual Add Guest form submission.
     * Route: POST /app/couple/guests/create
     *
     * @param Request $request
     * @return void
     */
    public function store(Request $request): void
    {
        $role = Session::get('role_snapshot') === 'crew' ? 'crew' : 'couple';
        $workspaceId = (int) Session::get('active_workspace_id');
        $postData = $request->allPost();

        // ── Guest Limit Validation ──
        $currentCount = $this->model->getGuestsCount($workspaceId);
        $db = \App\Core\Database::getConnection();
        $stmt = $db->prepare('
            SELECT COALESCE(w.custom_guest_limit, cp.max_guests) as guest_limit 
            FROM workspaces w
            JOIN commercial_plans cp ON cp.id = w.commercial_plan_id
            WHERE w.id = :workspace_id
        ');
        $stmt->execute(['workspace_id' => $workspaceId]);
        $limit = (int) ($stmt->fetchColumn() ?: 1000);

        if ($currentCount >= $limit) {
            Session::set('errors', ['full_name' => "You have reached the maximum guest limit of {$limit} allowed under your plan."]);
            Session::set('old_input', $postData);
            $this->flash('error', "Guest limit exceeded. Your plan allows up to {$limit} guests.");
            $this->redirect("/app/{$role}/guests/create");
            return;
        }

        $validator = new GuestValidator();
        $mappedRow = [
            'honorific'    => trim((string) ($postData['honorific'] ?? '')),
            'full_name'    => trim((string) ($postData['full_name'] ?? '')),
            'phone_number' => preg_replace('/[\s\-\(\)]/', '', trim((string) ($postData['phone_number'] ?? ''))),
            'invited_pax'  => trim((string) ($postData['invited_pax'] ?? '1')),
            'guest_group'  => trim((string) ($postData['guest_group'] ?? 'others'))
        ];

        $errors = $validator->validateRow($mappedRow);

        // Duplication validations
        if (empty($errors['full_name']) && $this->model->guestExistsByName($workspaceId, $mappedRow['full_name'])) {
            $errors['full_name'] = 'A guest with this name is already registered.';
        }
        if (empty($errors['phone_number']) && $mappedRow['phone_number'] !== '') {
            if ($this->model->guestExistsByPhone($workspaceId, $mappedRow['phone_number'])) {
                $errors['phone_number'] = 'This phone number is already registered to another guest.';
            }
        }

        if (!empty($errors)) {
            Session::set('errors', $errors);
            Session::set('old_input', $postData);
            $this->flash('error', 'Please correct the validation issues.');
            $this->redirect("/app/{$role}/guests/create");
            return;
        }

        try {
            // Generate 5-character uppercase alphanumeric Guest Code (e.g. A7K9Q) via central generator
            $token = $this->service->generateUniqueGuestCode($workspaceId);

            $guestId = $this->model->insertGuest($workspaceId, [
                'guest_code'   => $token,
                'honorific'    => $mappedRow['honorific'],
                'full_name'    => $mappedRow['full_name'],
                'side'         => trim((string) ($postData['side'] ?? 'couple')),
                'guest_group'  => $mappedRow['guest_group'],
                'invited_pax'  => (int) $mappedRow['invited_pax'],
                'phone_number' => $mappedRow['phone_number'],
                'notes'        => trim((string) ($postData['notes'] ?? ''))
            ]);

            // Save Guest Event Access & Session Assignments
            $selectedEventIds      = $postData['event_ids'] ?? [];
            $selectedEventSessions = $postData['event_sessions'] ?? [];
            $this->model->saveGuestEvents($workspaceId, $guestId, $selectedEventIds, $selectedEventSessions);

            $this->flash('success', 'Guest ' . $mappedRow['full_name'] . ' added successfully.');
            $this->redirect("/app/{$role}/guests");
        } catch (\Throwable $e) {
            $this->flash('error', 'Failed to add guest: ' . $e->getMessage());
            Session::set('old_input', $postData);
            $this->redirect("/app/{$role}/guests/create");
        }
    }

    /**
     * Show manual Edit Guest form view.
     * Route: GET /app/couple/guests/edit/{id}
     *
     * @param Request $request
     * @param string $id
     * @return void
     */
    public function edit(Request $request): void
    {
        $role = Session::get('role_snapshot') === 'crew' ? 'crew' : 'couple';
        $workspaceId = (int) Session::get('active_workspace_id');
        $id = $request->param('id');
        $guestId = (int) $id;
        $activeEventId = (int) $request->get('event_id', 0);

        $guest = $this->model->getGuestById($workspaceId, $guestId);
        if ($guest === null) {
            $this->flash('error', 'Guest not found.');
            $this->redirect("/app/{$role}/guests");
            return;
        }

        // Fetch workspace events, event sessions, and current guest event access
        $db = \App\Core\Database::getConnection();
        $stmt = $db->prepare('SELECT id, event_name FROM wedding_events WHERE workspace_id = :workspace_id AND is_deleted = 0 ORDER BY sort_order ASC');
        $stmt->execute(['workspace_id' => $workspaceId]);
        $workspaceEvents = $stmt->fetchAll(\PDO::FETCH_ASSOC);
        $allowedEventIds = $this->model->getGuestEventIds($workspaceId, $guestId);
        $allowedEventAssignments = $this->model->getGuestEventAssignments($workspaceId, $guestId);

        $eventSessionModel = new \App\Modules\Invitation\EventSessionModel();
        $eventSessionModel->ensureDefaultSessions($workspaceId);
        $eventSessions = $eventSessionModel->getSessionsGroupedByEvent($workspaceId);

        $this->render('guest/edit', [
            'title'                    => 'Edit Guest',
            'guest'                    => $guest,
            'errors'                   => Session::get('errors') ?? [],
            'old'                      => Session::get('old_input') ?? [],
            'workspace_events'         => $workspaceEvents,
            'allowed_event_ids'        => $allowedEventIds,
            'allowed_event_assignments'=> $allowedEventAssignments,
            'event_sessions'           => $eventSessions,
            'active_event_id'          => $activeEventId
        ]);

        Session::set('errors', null);
        Session::set('old_input', null);
    }

    /**
     * Process manual Edit Guest form submission.
     * Route: POST /app/couple/guests/edit/{id}
     *
     * @param Request $request
     * @param string $id
     * @return void
     */
    public function update(Request $request): void
    {
        $role = Session::get('role_snapshot') === 'crew' ? 'crew' : 'couple';
        $workspaceId = (int) Session::get('active_workspace_id');
        $id = $request->param('id');
        $guestId = (int) $id;
        $postData = $request->allPost();
        $redirectEventId = (int) ($postData['event_id'] ?? 0);

        $guest = $this->model->getGuestById($workspaceId, $guestId);
        if ($guest === null) {
            $this->flash('error', 'Guest not found.');
            $this->redirect("/app/{$role}/guests");
            return;
        }

        $validator = new GuestValidator();
        $mappedRow = [
            'honorific'    => trim((string) ($postData['honorific'] ?? '')),
            'full_name'    => trim((string) ($postData['full_name'] ?? '')),
            'phone_number' => preg_replace('/[\s\-\(\)]/', '', trim((string) ($postData['phone_number'] ?? ''))),
            'invited_pax'  => trim((string) ($postData['invited_pax'] ?? '1')),
            'guest_group'  => trim((string) ($postData['guest_group'] ?? 'others'))
        ];

        $errors = $validator->validateRow($mappedRow);

        // Duplication validations excluding current guest
        if (empty($errors['full_name']) && $this->model->guestExistsByName($workspaceId, $mappedRow['full_name'], $guestId)) {
            $errors['full_name'] = 'A guest with this name is already registered.';
        }
        if (empty($errors['phone_number']) && $mappedRow['phone_number'] !== '') {
            if ($this->model->guestExistsByPhone($workspaceId, $mappedRow['phone_number'], $guestId)) {
                $errors['phone_number'] = 'This phone number is already registered to another guest.';
            }
        }

        if (!empty($errors)) {
            Session::set('errors', $errors);
            Session::set('old_input', $postData);
            $this->flash('error', 'Please correct the validation issues.');
            $this->redirect("/app/{$role}/guests/edit/" . $guestId . ($redirectEventId > 0 ? "?event_id={$redirectEventId}" : ""));
            return;
        }

        try {
            $this->model->updateGuest($workspaceId, $guestId, [
                'honorific'    => $mappedRow['honorific'],
                'full_name'    => $mappedRow['full_name'],
                'phone_number' => $mappedRow['phone_number'],
                'guest_group'  => $mappedRow['guest_group'],
                'invited_pax'  => (int) $mappedRow['invited_pax'],
                'side'         => trim((string) ($postData['side'] ?? 'couple')),
                'notes'        => trim((string) ($postData['notes'] ?? ''))
            ]);

            // Save Guest Event Access & Session Assignments
            $selectedEventIds      = $postData['event_ids'] ?? [];
            $selectedEventSessions = $postData['event_sessions'] ?? [];
            $this->model->saveGuestEvents($workspaceId, $guestId, $selectedEventIds, $selectedEventSessions);

            $this->flash('success', 'Guest ' . $mappedRow['full_name'] . ' updated successfully.');
            $this->redirect("/app/{$role}/guests" . ($redirectEventId > 0 ? "?event_id={$redirectEventId}" : ""));
        } catch (\Throwable $e) {
            $this->flash('error', 'Failed to update guest: ' . $e->getMessage());
            Session::set('old_input', $postData);
            $this->redirect("/app/{$role}/guests/edit/" . $guestId . ($redirectEventId > 0 ? "?event_id={$redirectEventId}" : ""));
        }
    }

    /**
     * Process manual soft delete request.
     * Route: POST /app/couple/guests/delete/{id}
     *
     * @param Request $request
     * @param string $id
     * @return void
     */
    public function delete(Request $request): void
    {
        $role = Session::get('role_snapshot') === 'crew' ? 'crew' : 'couple';
        $workspaceId = (int) Session::get('active_workspace_id');
        $id = $request->param('id');
        $guestId = (int) $id;

        $guest = $this->model->getGuestById($workspaceId, $guestId);
        if ($guest === null) {
            $this->flash('error', 'Guest not found.');
            $this->redirect("/app/{$role}/guests");
            return;
        }

        try {
            $this->model->deleteGuest($workspaceId, $guestId);
            $this->flash('success', 'Guest ' . $guest['full_name'] . ' deleted successfully.');
        } catch (\Throwable $e) {
            $this->flash('error', 'Failed to delete guest: ' . $e->getMessage());
        }

        $this->redirect("/app/{$role}/guests");
    }

    /**
     * Show Excel upload interface.
     * Route: GET /app/couple/guests/import
     *
     * @param Request $request
     * @return void
     */
    public function showImport(Request $request): void
    {
        $this->render('guest/import', [
            'title'  => 'Import Guest List',
            'errors' => Session::get('errors') ?? []
        ]);

        Session::set('errors', null);
    }

    /**
     * Process spreadsheet upload, parse data, and render preview validation report.
     * Route: POST /app/couple/guests/import/preview
     *
     * @param Request $request
     * @return void
     */
    public function processImport(Request $request): void
    {
        $role = Session::get('role_snapshot') === 'crew' ? 'crew' : 'couple';
        $workspaceId = (int) Session::get('active_workspace_id');

        if (!isset($_FILES['guest_file']) || $_FILES['guest_file']['error'] === UPLOAD_ERR_NO_FILE) {
            Session::set('errors', ['guest_file' => 'Please select a file to upload.']);
            $this->redirect("/app/{$role}/guests/import");
            return;
        }

        $file = $_FILES['guest_file'];

        // Size check (limit 5MB)
        if ($file['size'] > 5 * 1024 * 1024) {
            Session::set('errors', ['guest_file' => 'The uploaded file exceeds the 5MB size limit.']);
            $this->redirect("/app/{$role}/guests/import");
            return;
        }

        // Extension check
        $originalName = (string) $file['name'];
        $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        if (!in_array($ext, ['csv', 'xlsx'], true)) {
            Session::set('errors', ['guest_file' => 'Unsupported format. Please upload a valid CSV or Excel (.xlsx) file.']);
            $this->redirect("/app/{$role}/guests/import");
            return;
        }

        try {
            $result = $this->service->validateImportFile($workspaceId, $file['tmp_name'], $originalName);

            // Save valid rows in session for final confirmation step
            Session::set('pending_import_rows', $result['valid_rows']);

            $this->render('guest/import_preview', [
                'title'   => 'Import Preview & Report',
                'report'  => $result['report'],
                'summary' => $result['summary'],
                'has_valid' => !empty($result['valid_rows'])
            ]);

        } catch (\Throwable $e) {
            Session::set('errors', ['guest_file' => $e->getMessage()]);
            $this->redirect("/app/{$role}/guests/import");
        }
    }

    /**
     * Finalize Excel import validation, write rows to database, and clear session.
     * Route: POST /app/couple/guests/import/confirm
     *
     * @param Request $request
     * @return void
     */
    public function confirmImport(Request $request): void
    {
        $role = Session::get('role_snapshot') === 'crew' ? 'crew' : 'couple';
        $workspaceId = (int) Session::get('active_workspace_id');
        $validRows = Session::get('pending_import_rows');

        if (empty($validRows)) {
            $this->flash('error', 'No valid rows found to import.');
            $this->redirect("/app/{$role}/guests/import");
            return;
        }

        try {
            $count = $this->service->finalizeImport($workspaceId, $validRows);
            $this->flash('success', 'Import completed successfully! ' . $count . ' guests imported.');
        } catch (\Throwable $e) {
            $this->flash('error', 'Failed to finalize import: ' . $e->getMessage());
        }

        Session::set('pending_import_rows', null);
        $this->redirect("/app/{$role}/guests");
    }

    /**
     * Stream CSV Excel-compatible template download.
     * Route: GET /app/couple/guests/template
     *
     * @param Request $request
     * @return void
     */
    public function downloadTemplate(Request $request): void
    {
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename=guest_list_template.csv');
        
        $output = fopen('php://output', 'w');
        fputcsv($output, ['Guest Name', 'Phone Number', 'Category', 'Invitation Name', 'Address', 'Pax', 'Notes']);
        fclose($output);
        exit;
    }

    /**
     * Download the high-resolution printable QR code for a guest.
     * Route: GET /app/couple/guests/qr/{id}
     *
     * @param Request $request
     * @return void
     */
    public function downloadQr(Request $request): void
    {
        $workspaceId = (int) Session::get('active_workspace_id');
        $id = $request->param('id');
        $guestId = (int) $id;

        $guest = $this->model->getGuestById($workspaceId, $guestId);
        if ($guest === null) {
            http_response_code(404);
            echo 'Guest not found.';
            exit;
        }

        $token = $guest['guest_code'];
        
        // Attempt high-res PNG (1000px)
        $pngData = \App\Services\QrCodeService::generateGuestPng($token, 1000);

        if (strpos(trim($pngData), '<svg') === 0) {
            // PNG failed, fallback SVG returned
            $svgData = \App\Services\QrCodeService::generateGuestSvg($token, 1000);
            header('Content-Type: image/svg+xml');
            header('Content-Disposition: attachment; filename="qr-' . $guest['guest_code'] . '.svg"');
            header('Content-Length: ' . strlen($svgData));
            echo $svgData;
            exit;
        }

        header('Content-Type: image/png');
        header('Content-Disposition: attachment; filename="qr-' . $guest['guest_code'] . '.png"');
        header('Content-Length: ' . strlen($pngData));
        echo $pngData;
        exit;
    }
}
