<?php

namespace App\Support;

use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class PromptLandingPageLeadPayload
{
    public const MAX_BYTES = 32768;

    private const MAX_DEPTH = 3;

    /**
     * @var list<string>
     */
    private const RESERVED_KEYS = ['_token', '_method', 'page_load_id', 'query_params'];

    /**
     * @return array<string, mixed>
     */
    public static function fromRequest(Request $request): array
    {
        $input = $request->isJson()
            ? $request->json()->all()
            : $request->except(self::RESERVED_KEYS);

        foreach (self::RESERVED_KEYS as $reserved) {
            unset($input[$reserved]);
        }

        self::validateLeadFields($input);

        $cleaned = self::clean($input, 0);

        if ($cleaned === []) {
            throw ValidationException::withMessages([
                'payload' => 'Please fill in the required fields.',
            ]);
        }

        $encoded = json_encode($cleaned);

        if ($encoded === false || strlen($encoded) > self::MAX_BYTES) {
            throw ValidationException::withMessages([
                'payload' => 'That submission is too large.',
            ]);
        }

        return $cleaned;
    }

    public static function pageLoadId(Request $request): ?string
    {
        $id = $request->input('page_load_id');

        if (! is_string($id) || $id === '') {
            return null;
        }

        if (! Str::isUuid($id)) {
            throw ValidationException::withMessages([
                'page_load_id' => 'The page load id is invalid.',
            ]);
        }

        return $id;
    }

    /**
     * @param  array<string, mixed>  $query
     * @return array<string, mixed>
     */
    public static function fromQueryArray(array $query): array
    {
        $cleaned = self::clean($query, 0);
        $encoded = json_encode($cleaned);

        if ($encoded === false || strlen($encoded) > self::MAX_BYTES) {
            throw ValidationException::withMessages([
                'query_params' => 'Those tracking parameters are too large.',
            ]);
        }

        return $cleaned;
    }

    public static function looksLikeEmailField(string $name, string $type = ''): bool
    {
        if (strtolower($type) === 'email') {
            return true;
        }

        return str_contains(self::normalizedFieldName($name), 'email');
    }

    public static function looksLikePhoneField(string $name, string $type = ''): bool
    {
        if (strtolower($type) === 'tel') {
            return true;
        }

        $name = self::normalizedFieldName($name);

        return (bool) preg_match('/(^|_)(phone|tel|mobile|cellphone|cell)(_|$)/', $name);
    }

    public static function looksLikeNameField(string $name): bool
    {
        $name = self::normalizedFieldName($name);

        if ((bool) preg_match('/(company|brand|user|file|org|organization|business)_?names?/', $name)) {
            return false;
        }

        return in_array($name, ['name', 'full_name', 'fullname', 'first_name', 'last_name', 'firstname', 'lastname', 'your_name'], true)
            || (bool) preg_match('/(^|_)(full_|first_|last_|your_)?name(_|$)/', $name);
    }

    public static function isOptionalLeadField(string $name): bool
    {
        $name = self::normalizedFieldName($name);

        return (bool) preg_match('/(^|_)(message|notes?|comment|comments|company|organisation|organization|website|url|details|additional)(_|$)/', $name);
    }

    /**
     * @param  array<mixed>  $input
     */
    private static function validateLeadFields(array $input): void
    {
        foreach ($input as $key => $value) {
            if ($value instanceof UploadedFile) {
                continue;
            }

            if (is_array($value)) {
                self::validateLeadFields($value);

                continue;
            }

            $name = self::cleanKey((string) $key);

            if ($name === '' || in_array($name, self::RESERVED_KEYS, true)) {
                continue;
            }

            if (! is_scalar($value) && $value !== null) {
                continue;
            }

            $text = self::cleanString((string) $value);

            if (self::looksLikeEmailField($name)) {
                if ($text === '' || filter_var($text, FILTER_VALIDATE_EMAIL) === false) {
                    throw ValidationException::withMessages([
                        'email' => 'Please enter a valid email address.',
                    ]);
                }

                continue;
            }

            if (self::looksLikeNameField($name) && $text === '') {
                throw ValidationException::withMessages([
                    'name' => 'Please fill in the required fields.',
                ]);
            }

            if (self::looksLikePhoneField($name) && ($text === '' || ! self::isValidPhone($text))) {
                throw ValidationException::withMessages([
                    'phone' => 'Please enter a valid phone number.',
                ]);
            }
        }
    }

    public static function isValidPhone(string $value): bool
    {
        $digits = preg_replace('/\D+/', '', $value) ?? '';

        return strlen($digits) >= 7 && strlen($digits) <= 15
            && (bool) preg_match('/^\+?[0-9()\s.\-]{7,20}$/', $value);
    }

    /**
     * @param  array<mixed>  $input
     * @return array<string, mixed>
     */
    private static function clean(array $input, int $depth): array
    {
        if ($depth > self::MAX_DEPTH) {
            return [];
        }

        $cleaned = [];

        foreach ($input as $key => $value) {
            if ($value instanceof UploadedFile) {
                continue;
            }

            $name = self::cleanKey((string) $key);

            if ($name === '' || in_array($name, self::RESERVED_KEYS, true)) {
                continue;
            }

            if (is_array($value)) {
                $nested = self::clean($value, $depth + 1);

                if ($nested !== []) {
                    $cleaned[$name] = $nested;
                }

                continue;
            }

            if (is_bool($value) || is_int($value) || is_float($value)) {
                $cleaned[$name] = $value;

                continue;
            }

            if (! is_scalar($value) && $value !== null) {
                continue;
            }

            $text = self::cleanString((string) $value);

            if ($text === '') {
                continue;
            }

            $cleaned[$name] = $text;
        }

        return $cleaned;
    }

    private static function normalizedFieldName(string $name): string
    {
        $name = strtolower(trim($name));
        $name = preg_replace('/[\s\-]+/', '_', $name) ?? $name;

        return trim($name, '_');
    }

    private static function cleanKey(string $key): string
    {
        $key = preg_replace('/[^\P{C}]+/u', '', $key) ?? '';

        return trim($key);
    }

    private static function cleanString(string $value): string
    {
        $value = preg_replace('/[^\P{C}\n\t]+/u', '', $value) ?? '';

        return trim($value);
    }
}
