<?php
declare(strict_types=1);

namespace App\Controller;

use App\Mailer\ContactMailer;
use App\Model\Table\ContactMessagesTable;
use Cake\Core\Configure;
use Cake\Http\Client;
use Cake\Http\Response;
use Cake\Log\Log;
use Cake\ORM\TableRegistry;
use Throwable;

class ContactMessagesController extends AppController
{
    protected ContactMessagesTable $ContactMessages;

    public function initialize(): void
    {
        parent::initialize();
        $this->ContactMessages = TableRegistry::getTableLocator()->get('ContactMessages');
        $this->FormProtection->setConfig('unlockedFields', ['cf-turnstile-response', 'website']);
    }

    public function add(): ?Response
    {
        $contactMessage = $this->ContactMessages->newEmptyEntity();

        if ($this->request->is('post')) {
            $data = $this->request->getData();
            $turnstileToken = trim((string)($data['cf-turnstile-response'] ?? ''));
            $honeypotValue = trim((string)($data['website'] ?? ''));
            unset($data['cf-turnstile-response'], $data['website']);

            // Coerce checkbox values to booleans
            $data['marketing_opt_in_email'] = !empty($data['marketing_opt_in_email']);
            $data['marketing_opt_in_sms'] = !empty($data['marketing_opt_in_sms']);

            $contactMessage = $this->ContactMessages->patchEntity($contactMessage, $data);

            if ($honeypotValue !== '') {
                $contactMessage->setError('turnstile', ['Please review your submission and try again.']);
                $this->Flash->error('Please review your submission and try again.');
            } else {
                [$isVerified, $verificationMessage] = $this->verifyTurnstileToken($turnstileToken);

                if (!$isVerified) {
                    $message = $verificationMessage ?? 'Security verification failed. Please try again.';
                    $contactMessage->setError('turnstile', [$message]);
                    $this->Flash->error($message);
                } elseif ($this->ContactMessages->save($contactMessage)) {
                    $this->sendConfirmationEmail($contactMessage);
                    $this->Flash->success('Your enquiry has been sent.');

                    return $this->redirect(['action' => 'success']);
                } else {
                    $this->Flash->error('Your enquiry could not be sent. Please correct the highlighted fields.');
                }
            }
        }

        $interestOptions = [
            'pottery' => 'Pottery',
            'knitting' => 'Knitting',
            'both' => 'Both',
            'not_sure' => 'Not sure yet',
        ];
        $experienceOptions = [
            'beginner' => 'Brand new',
            'some' => 'Tried it before',
            'returning' => 'Picking it back up',
            'advanced' => 'Experienced',
        ];

        $turnstileSiteKey = (string)Configure::read('Turnstile.siteKey');
        $this->set(compact('contactMessage', 'interestOptions', 'experienceOptions', 'turnstileSiteKey'));

        return null;
    }

    public function success(): void
    {
    }

    private function sendConfirmationEmail(\App\Model\Entity\ContactMessage $message): void
    {
        try {
            $mailer = new ContactMailer();
            $mailer->enquiryConfirmation($message)->deliver();
        } catch (Throwable $e) {
            Log::warning('Enquiry confirmation email failed: ' . $e->getMessage());
        }
    }

    /**
     * @return array{0: bool, 1: string|null}
     */
    private function verifyTurnstileToken(string $token): array
    {
        if ((bool)Configure::read('Turnstile.skipVerification', false)) {
            return [true, null];
        }

        if ($token === '') {
            return [false, 'Please complete the security check before submitting your enquiry.'];
        }

        $secretKey = (string)Configure::read('Turnstile.secretKey');
        $verifyUrl = (string)Configure::read('Turnstile.verifyUrl');

        if ($secretKey === '' || $verifyUrl === '') {
            return [false, 'Security verification is unavailable right now. Please try again shortly.'];
        }

        try {
            $http = new Client(['timeout' => 8]);
            $response = $http->post(
                $verifyUrl,
                [
                    'secret' => $secretKey,
                    'response' => $token,
                    'remoteip' => (string)$this->request->clientIp(),
                ],
                ['type' => 'form']
            );
        } catch (Throwable) {
            return [false, $this->verificationUnavailableMessage()];
        }

        if (!$response->isOk()) {
            return [false, $this->verificationUnavailableMessage()];
        }

        $payload = $response->getJson();
        if (!is_array($payload)) {
            return [false, $this->verificationUnavailableMessage()];
        }

        if (!empty($payload['success'])) {
            return [true, null];
        }

        $errorCodes = [];
        if (isset($payload['error-codes']) && is_array($payload['error-codes'])) {
            $errorCodes = array_map('strval', $payload['error-codes']);
        }

        return [false, $this->turnstileErrorMessage($errorCodes)];
    }

    private function verificationUnavailableMessage(): string
    {
        if ((bool)Configure::read('debug', false)) {
            return 'Security verification could not be completed right now. Check the Turnstile keys or internet connection and try again.';
        }

        return 'Security verification could not be completed right now. Please try again.';
    }

    /**
     * @param list<string> $errorCodes
     */
    private function turnstileErrorMessage(array $errorCodes): string
    {
        if (in_array('missing-input-response', $errorCodes, true)) {
            return 'Please complete the security check before submitting your enquiry.';
        }

        if (in_array('timeout-or-duplicate', $errorCodes, true)) {
            return 'The security check expired. Please try again.';
        }

        if (in_array('invalid-input-response', $errorCodes, true)) {
            return 'Security verification failed. Please refresh the page and try again.';
        }

        return 'Security verification failed. Please try again.';
    }
}
