<?php

namespace App\Services\Domain;

use App\Core\Registrar\AbstractRegistrar;
use App\Core\Registrar\RegistrarManager;
use App\Models\Tld;

/**
 * Server-side checkout guard: for every domain item in the basket, fetches
 * the registrar module's extra field schema and reports missing required /
 * invalid values per item index.
 *
 * Return shape:
 * [
 *     <itemIndex> => [
 *         'missing' => ['field_key', ...],
 *         'invalid' => ['field_key' => 'error message', ...],
 *     ],
 *     ...
 * ]
 *
 * Empty array = basket passes.
 *
 * TLD normalization: `tlds.tld` is stored with a LEADING DOT (e.g. `.tr`),
 * but the registrar module contract (`getExtraFields` / `validateExtraField`)
 * expects the dot-less canonical form (e.g. `tr`, `com.tr`). This service
 * strips the leading dot before handing the value to the module.
 */
class BasketExtraFieldsValidator
{
    public function __construct(
        private RegistrarManager $registrarManager,
        private ExtraFieldEvaluator $evaluator,
    ) {}

    public function validate(array $items): array
    {
        $errors = [];

        foreach ($items as $index => $item) {
            if (($item['type'] ?? null) !== 'domain') {
                continue;
            }

            $tldId = $item['tld_id'] ?? null;
            if (! $tldId) {
                continue;
            }

            $tld = Tld::find($tldId);
            if (! $tld || empty($tld->registrar)) {
                continue;
            }

            $module = $this->loadModule($tld->registrar);
            if (! $module) {
                continue;
            }

            // Canonical module contract uses dot-less TLD (`tr`, not `.tr`).
            $tldName = ltrim((string) $tld->tld, '.');

            $definitions = $module->getExtraFields($tldName);
            if (empty($definitions)) {
                continue;
            }

            $values = $item['additional_fields'] ?? [];

            $missing = $this->evaluator->missingRequired($definitions, $values);
            $invalid = $this->runValidation($module, $tldName, $definitions, $values);

            if (! empty($missing) || ! empty($invalid)) {
                $errors[$index] = array_filter([
                    'missing' => $missing,
                    'invalid' => $invalid,
                ]);
            }
        }

        return $errors;
    }

    private function loadModule(string $registrarName): ?AbstractRegistrar
    {
        // Mirror DomainProvisioningService::getRegistrarModule — RegistrarManager
        // keys modules by lowercase name and throws when unknown.
        $name = strtolower($registrarName);
        if (! $this->registrarManager->has($name)) {
            return null;
        }

        try {
            $module = $this->registrarManager->get($name);
            return $module instanceof AbstractRegistrar ? $module : null;
        } catch (\Throwable $e) {
            \Illuminate\Support\Facades\Log::warning(
                'BasketExtraFieldsValidator: failed to load registrar module',
                ['module' => $name, 'error' => $e->getMessage()],
            );
            return null;
        }
    }

    private function runValidation(
        AbstractRegistrar $module,
        string $tld,
        array $definitions,
        array $values,
    ): array {
        $invalid = [];
        $visibleDefs = $this->evaluator->filterVisible($definitions, $values);

        foreach ($visibleDefs as $key => $def) {
            $value = $values[$key] ?? null;
            // User-facing label — prepended to error messages so the customer
            // can tell which field failed when multiple extra fields exist.
            $label = $def['label'] ?? $key;

            // Skip empties that are not required — they're already caught
            // by missingRequired (for required ones) and irrelevant otherwise.
            if (($value === null || $value === '') && ! ($def['required'] ?? false)) {
                continue;
            }

            // Declarative validation (pattern + length) — only if value present.
            if ($value !== null && $value !== '' && isset($def['validation'])) {
                $declarativeError = $this->validateDeclarative($def['validation'], (string) $value);
                if ($declarativeError !== null) {
                    $invalid[$key] = "{$label}: {$declarativeError}";
                    continue;
                }
            }

            // Skip algorithmic validation when value empty (required-missing
            // already reported).
            if ($value === null || $value === '') {
                continue;
            }

            $result = $module->validateExtraField($tld, $key, $value, $values);
            if ($result !== true) {
                $msg = is_string($result) ? $result : __('admin/common.extra_field_invalid');
                $invalid[$key] = "{$label}: {$msg}";
            }
        }

        return $invalid;
    }

    private function validateDeclarative(array $rules, string $value): ?string
    {
        if (isset($rules['length'])) {
            $len = mb_strlen($value);
            if (is_array($rules['length'])) {
                [$min, $max] = $rules['length'];
                if ($len < $min || $len > $max) {
                    return __('admin/common.extra_field_length_invalid');
                }
            } elseif ($len !== (int) $rules['length']) {
                return __('admin/common.extra_field_length_invalid');
            }
        }

        if (isset($rules['pattern']) && ! preg_match($rules['pattern'], $value)) {
            return __('admin/common.extra_field_pattern_invalid');
        }

        return null;
    }
}
