<?php

namespace GriffinForms\Frontend\Security\FormValidations;

use GriffinForms\Includes\Integrations\FormSettingsManager;
use GriffinForms\Includes\Pipelines\Accounts\Contract;
use GriffinForms\Items\Field;

class AccountRegistration extends \GriffinForms\Frontend\Security\Format implements ValidationInterface
{
    public function getKey(): string
    {
        return 'account_registration';
    }

    public function getPriority(): int
    {
        return 20;
    }

    public function validate(int $form_id, array $context = []): array
    {
        $sanitized_page_data = isset($context['sanitized_page_data']) && is_array($context['sanitized_page_data'])
            ? $context['sanitized_page_data']
            : [];

        return $this->validateSanitizedPageData($form_id, $sanitized_page_data);
    }

    public function validateSanitizedPageData(int $form_id, array $sanitized_page_data): array
    {
        if ($form_id <= 0 || empty($sanitized_page_data)) {
            return [];
        }

        $rules = $this->getConfiguredRules($form_id);
        if (empty($rules)) {
            return [];
        }

        $errors = [];
        $protected_field_ids = $this->getProtectedEmailFieldIdsFromRules($rules);

        foreach ($protected_field_ids as $field_id) {
            if (!isset($sanitized_page_data[$field_id]) || !is_array($sanitized_page_data[$field_id])) {
                continue;
            }

            foreach ($sanitized_page_data[$field_id] as $iteration_index => $value) {
                $single_error = $this->validateSingleFieldValue($form_id, $field_id, $value, (int) $iteration_index);
                if (!$single_error['valid']) {
                    $errors[$field_id][(int) $iteration_index][] = $single_error['message'];
                }
            }
        }

        $errors = $this->mergeErrorSets(
            $errors,
            $this->validateIterableMappedFieldParity($sanitized_page_data, $rules)
        );

        return $errors;
    }

    public function validateSingleFieldValue(int $form_id, int $field_id, $value, ?int $iteration_index = null): array
    {
        if ($form_id <= 0 || $field_id <= 0) {
            return [
                'valid' => true,
                'message' => '',
                'code' => '',
            ];
        }

        $protected_field_ids = $this->getProtectedEmailFieldIds($form_id);
        if (!in_array($field_id, $protected_field_ids, true)) {
            return [
                'valid' => true,
                'message' => '',
                'code' => '',
            ];
        }

        $email = is_string($value) ? trim($value) : '';
        if ($email === '' || !is_email($email)) {
            return [
                'valid' => true,
                'message' => '',
                'code' => '',
            ];
        }

        if (!email_exists($email)) {
            return [
                'valid' => true,
                'message' => '',
                'code' => '',
            ];
        }

        return [
            'valid' => false,
            'message' => $this->buildFieldErrorMessage($field_id, $iteration_index),
            'code' => 'existing_user_found',
        ];
    }

    /**
     * @return array<int>
     */
    public function getProtectedEmailFieldIds(int $form_id): array
    {
        return $this->getProtectedEmailFieldIdsFromRules($this->getConfiguredRules($form_id));
    }

    private function buildFieldErrorMessage(int $field_id, ?int $iteration_index = null): string
    {
        $is_iterable = false;

        if ($field_id > 0) {
            $field = new Field();
            $field->loadItem($field_id);
            $iterations = $field->getProp('iterations');
            $is_iterable = is_array($iterations) && !empty($iterations['allow']);
        }

        if ($is_iterable && is_numeric($iteration_index) && (int) $iteration_index >= 0) {
            return sprintf(
                __('Entered email entry #%d already belongs to an existing account. Use a different email address to continue.', 'griffinforms-form-builder'),
                ((int) $iteration_index) + 1
            );
        }

        return __('Entered email already belongs to an existing account. Use a different email address to continue.', 'griffinforms-form-builder');
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    private function getConfiguredRules(int $form_id): array
    {
        if ($form_id <= 0) {
            return [];
        }

        $stored = FormSettingsManager::getInstance()->getIntegrationSettings($form_id, Contract::INTEGRATION_ID);
        $rules = isset($stored['settings']['rules']) && is_array($stored['settings']['rules'])
            ? $stored['settings']['rules']
            : [];

        return array_values(array_filter($rules, static function ($rule) {
            return is_array($rule);
        }));
    }

    /**
     * @param array<int,array<string,mixed>> $rules
     * @return array<int>
     */
    private function getProtectedEmailFieldIdsFromRules(array $rules): array
    {
        $field_ids = [];

        foreach ($rules as $rule) {
            $is_enabled = !array_key_exists('is_enabled', $rule) || !empty($rule['is_enabled']);
            if (!$is_enabled) {
                continue;
            }

            $existing_behavior = sanitize_key((string) ($rule['existing_account_behavior'] ?? 'error'));
            if ($existing_behavior !== 'error') {
                continue;
            }

            $email_field_id = absint($rule['email_field_id'] ?? 0);
            if ($email_field_id <= 0) {
                continue;
            }

            $field_ids[] = $email_field_id;
        }

        return array_values(array_unique($field_ids));
    }

    /**
     * @param array<string|int,mixed> $sanitized_page_data
     * @param array<int,array<string,mixed>> $rules
     * @return array<int,array<int,array<int,string>>>
     */
    private function validateIterableMappedFieldParity(array $sanitized_page_data, array $rules): array
    {
        $errors = [];

        foreach ($rules as $rule) {
            $is_enabled = !array_key_exists('is_enabled', $rule) || !empty($rule['is_enabled']);
            if (!$is_enabled) {
                continue;
            }

            $email_field_id = absint($rule['email_field_id'] ?? 0);
            if ($email_field_id <= 0 || !$this->fieldIsIterable($email_field_id)) {
                continue;
            }

            $email_values = $this->getSequenceValues($sanitized_page_data, $email_field_id);
            $target_indices = $this->getPopulatedIndices($email_values);
            if (empty($target_indices)) {
                continue;
            }

            foreach ($this->getIterableMappedFieldSpecs() as $spec) {
                $mode = (string) ($rule[$spec['mode_key']] ?? 'none');
                $expected_mode = (string) ($spec['expected_mode'] ?? 'map_field');
                if ($mode !== $expected_mode) {
                    continue;
                }

                $field_id = absint($rule[$spec['field_key']] ?? 0);
                if ($field_id <= 0 || !$this->fieldIsIterable($field_id)) {
                    continue;
                }

                $mapped_values = $this->getSequenceValues($sanitized_page_data, $field_id);
                foreach ($target_indices as $target_index) {
                    $mapped_value = isset($mapped_values[$target_index]) ? trim((string) $mapped_values[$target_index]) : '';
                    if ($mapped_value !== '') {
                        continue;
                    }

                    $errors[$field_id][$target_index][] = $this->buildIterableMappingShortfallMessage(
                        (string) $spec['label'],
                        $target_index
                    );
                }
            }
        }

        return $errors;
    }

    /**
     * @return array<string,array<string,string>>
     */
    private function getIterableMappedFieldSpecs(): array
    {
        return [
            'first_name' => [
                'mode_key' => 'first_name_mode',
                'field_key' => 'first_name_field_id',
                'expected_mode' => 'map_field',
                'label' => __('first name', 'griffinforms-form-builder'),
            ],
            'last_name' => [
                'mode_key' => 'last_name_mode',
                'field_key' => 'last_name_field_id',
                'expected_mode' => 'map_field',
                'label' => __('last name', 'griffinforms-form-builder'),
            ],
            'username' => [
                'mode_key' => 'username_mode',
                'field_key' => 'username_field_id',
                'expected_mode' => 'mapped_or_generate',
                'label' => __('username', 'griffinforms-form-builder'),
            ],
        ];
    }

    /**
     * @param array<string|int,mixed> $sanitized_page_data
     * @return array<int,string>
     */
    private function getSequenceValues(array $sanitized_page_data, int $field_id): array
    {
        if ($field_id <= 0) {
            return [];
        }

        $values = $sanitized_page_data[$field_id] ?? $sanitized_page_data[(string) $field_id] ?? [];
        if (!is_array($values)) {
            return [];
        }

        return array_values(array_map(static function ($value) {
            return is_scalar($value) ? trim((string) $value) : '';
        }, $values));
    }

    /**
     * @param array<int,string> $values
     * @return array<int>
     */
    private function getPopulatedIndices(array $values): array
    {
        $indices = [];

        foreach ($values as $index => $value) {
            if (trim((string) $value) === '') {
                continue;
            }

            $indices[] = (int) $index;
        }

        return $indices;
    }

    private function fieldIsIterable(int $field_id): bool
    {
        if ($field_id <= 0) {
            return false;
        }

        $field = new Field();
        if (!$field->loadItem($field_id)) {
            return false;
        }

        $iterations = $field->getProp('iterations');
        if (is_array($iterations)) {
            if (array_key_exists('allow', $iterations)) {
                return !empty($iterations['allow']);
            }

            if (array_key_exists('max', $iterations) && is_numeric($iterations['max'])) {
                return (int) $iterations['max'] > 1;
            }

            return !empty($iterations);
        }

        if (is_numeric($iterations)) {
            return (int) $iterations > 1;
        }

        if (is_bool($iterations)) {
            return $iterations;
        }

        return in_array(strtolower(trim((string) $iterations)), ['1', 'true', 'yes', 'on', 'multiple', 'iterable'], true);
    }

    private function buildIterableMappingShortfallMessage(string $mapping_label, int $target_index): string
    {
        return sprintf(
            __('Each entered email needs a matching %1$s entry. Add a %1$s for email entry #%2$d or remove the extra email entry.', 'griffinforms-form-builder'),
            $mapping_label,
            $target_index + 1
        );
    }

    /**
     * @param array<int,mixed> $existing
     * @param array<int,mixed> $incoming
     * @return array<int,mixed>
     */
    private function mergeErrorSets(array $existing, array $incoming): array
    {
        if (empty($incoming)) {
            return $existing;
        }

        foreach ($incoming as $field_id => $field_errors) {
            if (!isset($existing[$field_id]) || !is_array($existing[$field_id])) {
                $existing[$field_id] = [];
            }

            if (!is_array($field_errors)) {
                $existing[$field_id][] = $field_errors;
                continue;
            }

            foreach ($field_errors as $iteration_index => $iteration_errors) {
                if (!isset($existing[$field_id][$iteration_index]) || !is_array($existing[$field_id][$iteration_index])) {
                    $existing[$field_id][$iteration_index] = [];
                }

                if (!is_array($iteration_errors)) {
                    $existing[$field_id][$iteration_index][] = $iteration_errors;
                    continue;
                }

                $existing[$field_id][$iteration_index] = array_values(array_unique(array_merge(
                    $existing[$field_id][$iteration_index],
                    $iteration_errors
                )));
            }
        }

        return $existing;
    }
}
