<?php

declare(strict_types=1);

namespace App\Controllers\Public;

use App\Core\Controller;
use App\Core\Csrf;
use App\Core\Database;
use App\Core\Request;
use App\Services\QrCodeService;
use PDOException;
use Throwable;
use App\Services\RegistrationEmailService;

class RegistrationController extends Controller
{
    /**
     * Display the public registration form.
     */
    public function create(): void
    {
        $event = $this->getCurrentEvent();

        if (!$event) {
            http_response_code(404);

            $this->view('errors/404', [
                'pageTitle' => 'Registration Unavailable',
            ]);

            return;
        }

        $districts = Database::fetchAll(
            "
            SELECT
                id,
                name
            FROM districts
            WHERE is_active = 1
            ORDER BY name ASC
            "
        );

        $categories = Database::fetchAll(
            "
            SELECT
                id,
                name
            FROM delegate_categories
            WHERE event_id = :event_id
              AND is_active = 1
            ORDER BY display_order ASC, name ASC
            ",
            [
                'event_id' => $event['id'],
            ]
        );

        $municipalities = [];

        $oldDistrictId = (int) old(
            'district_id',
            0
        );

        if ($oldDistrictId > 0) {
            $municipalities = Database::fetchAll(
                "
                SELECT
                    id,
                    name
                FROM municipalities
                WHERE district_id = :district_id
                  AND is_active = 1
                ORDER BY name ASC
                ",
                [
                    'district_id' => $oldDistrictId,
                ]
            );
        }

        $this->view('public/register', [
            'pageTitle' => 'Conference Registration',
            'event' => $event,
            'districts' => $districts,
            'categories' => $categories,
            'municipalities' => $municipalities,
            'errors' => get_errors(),
        ]);
    }

    /**
     * Process and save the registration.
     */
    public function store(): void
    {
        if (
            !Csrf::verify(
                Request::string('_token')
            )
        ) {
            http_response_code(419);

            echo 'Your session has expired. Please refresh the page and try again.';

            return;
        }

        $event = $this->getCurrentEvent();

        if (!$event) {
            flash(
                'error',
                'Conference registration is currently unavailable.'
            );

            redirect('/register');
        }

        $validated = $this->validateRegistration(
            (int) $event['id']
        );

        if ($validated['errors'] !== []) {
            set_old_input(Request::all());
            set_errors($validated['errors']);

            redirect('/register');
        }

        $data = $validated['values'];

        if (
            $this->duplicateRegistrationExists(
                (int) $event['id'],
                $data['email'],
                $data['cell_number']
            )
        ) {
            set_old_input(Request::all());

            set_errors([
                'duplicate' =>
                    'A registration using this email address or cell number already exists.',
            ]);

            redirect('/register');
        }

        $registrationNumber =
            $this->generateRegistrationNumber(
                (int) $event['id']
            );

        $qrToken = bin2hex(
            random_bytes(32)
        );

        $qrCodePath = null;

        try {
            $qrService = new QrCodeService();

            $qrCodePath = $qrService->generateDelegateQr(
                $qrToken,
                $registrationNumber
            );
        } catch (Throwable $exception) {
            error_log(
                'QR-code generation failed: '
                . $exception->getMessage()
            );
        }

        $registrationStatus =
            (int) ($event['require_approval'] ?? 0) === 1
                ? 'pending'
                : 'approved';

        try {
            Database::connection()->beginTransaction();

            $delegateId = Database::insert(
                "
                INSERT INTO delegates (
                    event_id,
                    category_id,
                    district_id,
                    municipality_id,
                    registration_number,
                    title,
                    full_names,
                    first_name,
                    surname,
                    position_title,
                    organisation,
                    traditional_council,
                    email,
                    cell_number,
                    specific_challenge,
                    dietary_requirements,
                    accessibility_requirements,
                    attendance_days,
                    registration_source,
                    registration_status,
                    attendance_status,
                    email_status,
                    qr_token,
                    qr_code_path,
                    badge_path,
                    consent_given,
                    consent_given_at,
                    privacy_policy_version,
                    created_at,
                    updated_at
                ) VALUES (
                    :event_id,
                    :category_id,
                    :district_id,
                    :municipality_id,
                    :registration_number,
                    :title,
                    :full_names,
                    :first_name,
                    :surname,
                    :position_title,
                    :organisation,
                    :traditional_council,
                    :email,
                    :cell_number,
                    :specific_challenge,
                    :dietary_requirements,
                    :accessibility_requirements,
                    :attendance_days,
                    'public_form',
                    :registration_status,
                    'not_checked_in',
                    'not_sent',
                    :qr_token,
                    :qr_code_path,
                    NULL,
                    1,
                    NOW(),
                    '1.0',
                    NOW(),
                    NOW()
                )
                ",
                [
                    'event_id' => $event['id'],
                    'category_id' => $data['category_id'],
                    'district_id' => $data['district_id'],
                    'municipality_id' =>
                        $data['municipality_id'],
                    'registration_number' =>
                        $registrationNumber,
                    'title' => $data['title'],
                    'full_names' => $data['full_names'],
                    'first_name' => $data['first_name'],
                    'surname' => $data['surname'],
                    'position_title' =>
                        $data['position_title'],
                    'organisation' => $data['organisation'],
                    'traditional_council' =>
                        $data['traditional_council'],
                    'email' => $data['email'],
                    'cell_number' => $data['cell_number'],
                    'specific_challenge' =>
                        $data['specific_challenge'],
                    'dietary_requirements' =>
                        $data['dietary_requirements'],
                    'accessibility_requirements' =>
                        $data['accessibility_requirements'],
                    'attendance_days' =>
                        $data['attendance_days'],
                    'registration_status' =>
                        $registrationStatus,
                    'qr_token' => $qrToken,
                    'qr_code_path' => $qrCodePath,
                ]
            );

Database::connection()->commit();

$delegate = Database::fetch(
    "
    SELECT
        d.*,
        dc.name AS category_name
    FROM delegates d
    LEFT JOIN delegate_categories dc
        ON dc.id = d.category_id
    WHERE d.id = :id
    LIMIT 1
    ",
    [
        'id' => $delegateId,
    ]
);

$emailSent = false;

if ($delegate) {
    try {
        $registrationEmailService =
            new RegistrationEmailService();

        $emailSent =
            $registrationEmailService
                ->sendConfirmation(
                    $delegate,
                    $event
                );
    } catch (Throwable $exception) {
        error_log(
            'Confirmation email error: '
            . $exception->getMessage()
        );
    }
}

clear_old_input();
Csrf::regenerate();

$_SESSION['registration_success'] = [
    'delegate_id' => $delegateId,
    'registration_number' =>
        $registrationNumber,
    'full_names' =>
        $data['full_names'],
    'email' =>
        $data['email'],
    'qr_code_path' =>
        $qrCodePath,
    'status' =>
        $registrationStatus,
    'email_sent' =>
        $emailSent,
];

            redirect('/registration/success');
        } catch (PDOException $exception) {
            if (
                Database::connection()
                    ->inTransaction()
            ) {
                Database::connection()
                    ->rollBack();
            }

            error_log(
                'Registration database error: '
                . $exception->getMessage()
            );

            set_old_input(Request::all());

            set_errors([
                'system' =>
                    'Your registration could not be completed. Please try again.',
            ]);

            redirect('/register');
        }
    }

    /**
     * Display the registration success page.
     */
    public function success(): void
    {
        $registration =
            $_SESSION['registration_success']
            ?? null;

        if (!$registration) {
            redirect('/register');
        }

        $event = $this->getCurrentEvent();

        $this->view(
            'public/registration-success',
            [
                'pageTitle' =>
                    'Registration Successful',
                'event' => $event,
                'registration' =>
                    $registration,
            ]
        );

        unset(
            $_SESSION['registration_success']
        );
    }

    /**
     * Return municipalities as JSON.
     */
    public function municipalities(
        string $districtId
    ): void {
        header(
            'Content-Type: application/json; charset=utf-8'
        );

        $districtId = (int) $districtId;

        if ($districtId <= 0) {
            echo json_encode([]);

            return;
        }

        $municipalities = Database::fetchAll(
            "
            SELECT
                id,
                name
            FROM municipalities
            WHERE district_id = :district_id
              AND is_active = 1
            ORDER BY name ASC
            ",
            [
                'district_id' => $districtId,
            ]
        );

        echo json_encode(
            $municipalities,
            JSON_UNESCAPED_SLASHES |
            JSON_UNESCAPED_UNICODE
        );
    }

    private function getCurrentEvent(): ?array
    {
        return Database::fetch(
            "
            SELECT *
            FROM events
            WHERE status = 'published'
              AND allow_public_registration = 1
            ORDER BY start_date ASC
            LIMIT 1
            "
        );
    }

    private function validateRegistration(
        int $eventId
    ): array {
        $title = Request::string('title');

        $fullNames = Request::string(
            'full_names'
        );

        $positionTitle = Request::string(
            'position_title'
        );

        $organisation = Request::string(
            'organisation'
        );

        $traditionalCouncil =
            Request::string(
                'traditional_council'
            );

        $categoryId = Request::integer(
            'category_id'
        );

        $districtId = Request::integer(
            'district_id'
        );

        $municipalityId =
            Request::integer(
                'municipality_id'
            );

        $cellNumber = Request::string(
            'cell_number'
        );

        $email = strtolower(
            Request::string('email')
        );

        $specificChallenge =
            Request::string(
                'specific_challenge'
            );

        $dietaryRequirements =
            Request::string(
                'dietary_requirements'
            );

        $accessibilityRequirements =
            Request::string(
                'accessibility_requirements'
            );

        $attendanceDays = Request::input(
            'attendance_days',
            []
        );

        $consentGiven = Request::string(
            'consent_given'
        );

        $errors = [];

        $allowedTitles = [
            'Adv',
            'Cllr',
            'Dr',
            'Hon',
            'Mr',
            'Mrs',
            'Ms',
            'Prof',
            'Kgosi',
            'Kgoshi',
            'Chief',
            'Inkosi',
            'Other',
        ];

        if (
            $title !== '' &&
            !in_array(
                $title,
                $allowedTitles,
                true
            )
        ) {
            $errors['title'] =
                'Please select a valid title.';
        }

        if ($fullNames === '') {
            $errors['full_names'] =
                'Full names are required.';
        } elseif (
            mb_strlen($fullNames) > 200
        ) {
            $errors['full_names'] =
                'Full names may not exceed 200 characters.';
        }

        if ($positionTitle === '') {
            $errors['position_title'] =
                'Your title or position is required.';
        }

        if (!$categoryId) {
            $errors['category_id'] =
                'Please select a delegate category.';
        } elseif (
            !$this->validCategory(
                $eventId,
                $categoryId
            )
        ) {
            $errors['category_id'] =
                'The selected delegate category is invalid.';
        }

        if (!$districtId) {
            $errors['district_id'] =
                'Please select your district.';
        } elseif (
            !$this->validDistrict(
                $districtId
            )
        ) {
            $errors['district_id'] =
                'The selected district is invalid.';
        }

        if (
            $municipalityId &&
            !$this->validMunicipality(
                $districtId,
                $municipalityId
            )
        ) {
            $errors['municipality_id'] =
                'The selected municipality is invalid.';
        }

        if ($cellNumber === '') {
            $errors['cell_number'] =
                'A cell number is required.';
        } elseif (
            !preg_match(
                '/^[0-9+\s()\-]{9,20}$/',
                $cellNumber
            )
        ) {
            $errors['cell_number'] =
                'Please enter a valid cell number.';
        }

        if ($email === '') {
            $errors['email'] =
                'An email address is required.';
        } elseif (
            !filter_var(
                $email,
                FILTER_VALIDATE_EMAIL
            )
        ) {
            $errors['email'] =
                'Please enter a valid email address.';
        }

        if (!is_array($attendanceDays)) {
            $attendanceDays = [];
        }

        $attendanceDays = array_values(
            array_intersect(
                array_map(
                    'strval',
                    $attendanceDays
                ),
                [
                    'day_1',
                    'day_2',
                ]
            )
        );

        if ($attendanceDays === []) {
            $errors['attendance_days'] =
                'Please select at least one attendance day.';
        }

        if ($consentGiven !== '1') {
            $errors['consent_given'] =
                'You must provide consent before registering.';
        }

        [$firstName, $surname] =
            $this->splitName(
                $fullNames
            );

        return [
            'errors' => $errors,

            'values' => [
                'title' =>
                    $title !== ''
                        ? $title
                        : null,

                'full_names' =>
                    $fullNames,

                'first_name' =>
                    $firstName,

                'surname' =>
                    $surname,

                'position_title' =>
                    $positionTitle,

                'organisation' =>
                    $organisation !== ''
                        ? $organisation
                        : null,

                'traditional_council' =>
                    $traditionalCouncil !== ''
                        ? $traditionalCouncil
                        : null,

                'category_id' =>
                    $categoryId,

                'district_id' =>
                    $districtId,

                'municipality_id' =>
                    $municipalityId,

                'cell_number' =>
                    $this->normalisePhone(
                        $cellNumber
                    ),

                'email' =>
                    $email,

                'specific_challenge' =>
                    $specificChallenge !== ''
                        ? $specificChallenge
                        : null,

                'dietary_requirements' =>
                    $dietaryRequirements !== ''
                        ? $dietaryRequirements
                        : null,

                'accessibility_requirements' =>
                    $accessibilityRequirements !== ''
                        ? $accessibilityRequirements
                        : null,

                'attendance_days' =>
                    implode(
                        ',',
                        $attendanceDays
                    ),
            ],
        ];
    }

    private function duplicateRegistrationExists(
        int $eventId,
        string $email,
        string $cellNumber
    ): bool {
        $normalisedCell =
            preg_replace(
                '/[^0-9+]/',
                '',
                $cellNumber
            );

        $record = Database::fetch(
            "
            SELECT id
            FROM delegates
            WHERE event_id = :event_id
              AND registration_status != 'cancelled'
              AND (
                    LOWER(email) = :email
                    OR REPLACE(
                        REPLACE(
                            REPLACE(
                                REPLACE(
                                    cell_number,
                                    ' ',
                                    ''
                                ),
                                '-',
                                ''
                            ),
                            '(',
                            ''
                        ),
                        ')',
                        ''
                    ) = :cell_number
              )
            LIMIT 1
            ",
            [
                'event_id' => $eventId,
                'email' => strtolower($email),
                'cell_number' =>
                    $normalisedCell,
            ]
        );

        return $record !== null;
    }

    private function generateRegistrationNumber(
        int $eventId
    ): string {
        $year = date('Y');

        $prefix = 'TA-' . $year . '-';

        $lastRegistration =
            Database::fetch(
                "
                SELECT registration_number
                FROM delegates
                WHERE event_id = :event_id
                  AND registration_number
                      LIKE :prefix
                ORDER BY id DESC
                LIMIT 1
                ",
                [
                    'event_id' =>
                        $eventId,

                    'prefix' =>
                        $prefix . '%',
                ]
            );

        $nextNumber = 1;

        if (
            !empty(
                $lastRegistration[
                    'registration_number'
                ]
            )
        ) {
            $lastNumber = (int) substr(
                $lastRegistration[
                    'registration_number'
                ],
                -6
            );

            $nextNumber =
                $lastNumber + 1;
        }

        return $prefix
            . str_pad(
                (string) $nextNumber,
                6,
                '0',
                STR_PAD_LEFT
            );
    }

    private function validCategory(
        int $eventId,
        int $categoryId
    ): bool {
        return Database::fetch(
            "
            SELECT id
            FROM delegate_categories
            WHERE id = :id
              AND event_id = :event_id
              AND is_active = 1
            LIMIT 1
            ",
            [
                'id' => $categoryId,
                'event_id' => $eventId,
            ]
        ) !== null;
    }

    private function validDistrict(
        int $districtId
    ): bool {
        return Database::fetch(
            "
            SELECT id
            FROM districts
            WHERE id = :id
              AND is_active = 1
            LIMIT 1
            ",
            [
                'id' => $districtId,
            ]
        ) !== null;
    }

    private function validMunicipality(
        ?int $districtId,
        int $municipalityId
    ): bool {
        if (!$districtId) {
            return false;
        }

        return Database::fetch(
            "
            SELECT id
            FROM municipalities
            WHERE id = :id
              AND district_id = :district_id
              AND is_active = 1
            LIMIT 1
            ",
            [
                'id' => $municipalityId,
                'district_id' => $districtId,
            ]
        ) !== null;
    }

    private function splitName(
        string $fullNames
    ): array {
        $parts = preg_split(
            '/\s+/',
            trim($fullNames)
        );

        if (
            !$parts ||
            count($parts) === 1
        ) {
            return [
                $fullNames,
                null,
            ];
        }

        $surname = array_pop($parts);

        $firstName = implode(
            ' ',
            $parts
        );

        return [
            $firstName,
            $surname,
        ];
    }

    private function normalisePhone(
        string $phone
    ): string {
        $phone = preg_replace(
            '/[^0-9+]/',
            '',
            $phone
        );

        if (
            is_string($phone) &&
            str_starts_with(
                $phone,
                '0'
            ) &&
            strlen($phone) === 10
        ) {
            return '+27'
                . substr(
                    $phone,
                    1
                );
        }

        return (string) $phone;
    }
}