<?php

namespace MintyPHP\Service\CustomField;

use MintyPHP\Repository\CustomField\TenantCustomFieldDefinitionRepositoryInterface;
use MintyPHP\Repository\CustomField\TenantCustomFieldOptionRepositoryInterface;
use MintyPHP\Repository\Tenant\TenantRepository;

class TenantCustomFieldService
{
    private const ALLOWED_TYPES = ['text', 'textarea', 'select', 'multiselect', 'boolean', 'date'];

    public function __construct(
        private readonly TenantRepository $tenantRepository,
        private readonly TenantCustomFieldDefinitionRepositoryInterface $definitionRepository,
        private readonly TenantCustomFieldOptionRepositoryInterface $optionRepository
    ) {
    }

    public function listForTenant(int $tenantId): array
    {
        $tenantId = (int) $tenantId;
        if ($tenantId <= 0) {
            return [];
        }

        $definitions = $this->definitionRepository->listByTenantId($tenantId, false);
        if (!$definitions) {
            return [];
        }

        $definitionIds = array_map(static fn (array $row): int => (int) ($row['id'] ?? 0), $definitions);
        $definitionIds = array_values(array_filter($definitionIds, static fn (int $id): bool => $id > 0));
        $options = $this->optionRepository->listByDefinitionIds($definitionIds, false);
        $optionsByDefinition = [];
        foreach ($options as $option) {
            $definitionId = (int) ($option['definition_id'] ?? 0);
            if ($definitionId <= 0) {
                continue;
            }
            $optionsByDefinition[$definitionId] ??= [];
            $optionsByDefinition[$definitionId][] = $option;
        }

        foreach ($definitions as &$definition) {
            $definitionId = (int) ($definition['id'] ?? 0);
            $definition['options'] = $optionsByDefinition[$definitionId] ?? [];
        }
        unset($definition);

        return $definitions;
    }

    public function createForTenant(int $tenantId, array $input, int $currentUserId): array
    {
        if ($tenantId <= 0 || !$this->tenantRepository->find($tenantId)) {
            return ['ok' => false, 'errors' => [t('Tenant not found')]];
        }

        $form = self::sanitizeForm($input);
        $form['tenant_id'] = $tenantId;
        $form['field_key'] = $this->resolveFieldKey(
            $tenantId,
            (string) ($form['label'] ?? ''),
            null,
            (string) ($form['field_key'] ?? '')
        );
        $errors = $this->validateForm($form, null);
        if ($errors) {
            return ['ok' => false, 'errors' => $errors, 'form' => $form];
        }

        $definitionId = $this->definitionRepository->create([
            'tenant_id' => $tenantId,
            'field_key' => $form['field_key'],
            'label' => $form['label'],
            'type' => $form['type'],
            'is_required' => $form['is_required'],
            'is_filterable' => $form['is_filterable'],
            'active' => $form['active'],
            'sort_order' => $form['sort_order'] ?? $this->nextSortOrder($tenantId),
            'created_by' => $currentUserId > 0 ? $currentUserId : null,
        ]);
        if (!$definitionId) {
            return ['ok' => false, 'errors' => [t('Custom field can not be created')], 'form' => $form];
        }

        if (in_array($form['type'], ['select', 'multiselect'], true)) {
            $this->optionRepository->replaceForDefinition((int) $definitionId, $form['options']);
        }

        return ['ok' => true, 'id' => (int) $definitionId, 'form' => $form];
    }

    public function updateByUuid(string $uuid, array $input, int $currentUserId): array
    {
        $uuid = trim($uuid);
        if ($uuid === '') {
            return ['ok' => false, 'errors' => [t('Custom field not found')]];
        }

        $existing = $this->definitionRepository->findByUuid($uuid);
        if (!$existing) {
            return ['ok' => false, 'errors' => [t('Custom field not found')]];
        }

        $form = self::sanitizeForm($input);
        $form['tenant_id'] = (int) ($existing['tenant_id'] ?? 0);
        $form['field_key'] = $this->resolveFieldKey(
            (int) ($existing['tenant_id'] ?? 0),
            (string) ($form['label'] ?? ''),
            (int) ($existing['id'] ?? 0),
            (string) ($existing['field_key'] ?? '')
        );
        $errors = $this->validateForm($form, (int) ($existing['id'] ?? 0));
        if ($errors) {
            return ['ok' => false, 'errors' => $errors, 'form' => $form];
        }

        $updated = $this->definitionRepository->update((int) $existing['id'], [
            'field_key' => $form['field_key'],
            'label' => $form['label'],
            'type' => $form['type'],
            'is_required' => $form['is_required'],
            'is_filterable' => $form['is_filterable'],
            'active' => $form['active'],
            'sort_order' => $form['sort_order'] ?? (int) ($existing['sort_order'] ?? 100),
            'modified_by' => $currentUserId > 0 ? $currentUserId : null,
        ]);
        if (!$updated) {
            return ['ok' => false, 'errors' => [t('Custom field can not be updated')], 'form' => $form];
        }

        // When type is changed away from select/multiselect, remove orphaned option rows.
        if (in_array($form['type'], ['select', 'multiselect'], true)) {
            $this->optionRepository->replaceForDefinition((int) $existing['id'], $form['options']);
        } else {
            $this->optionRepository->deleteByDefinitionId((int) $existing['id']);
        }

        return ['ok' => true, 'form' => $form];
    }

    public function deleteByUuid(string $uuid): array
    {
        $uuid = trim($uuid);
        if ($uuid === '') {
            return ['ok' => false, 'status' => 404, 'error' => 'not_found'];
        }
        $existing = $this->definitionRepository->findByUuid($uuid);
        if (!$existing || !isset($existing['id'])) {
            return ['ok' => false, 'status' => 404, 'error' => 'not_found'];
        }
        $deleted = $this->definitionRepository->delete((int) $existing['id']);
        if (!$deleted) {
            return ['ok' => false, 'status' => 500, 'error' => 'delete_failed'];
        }
        return ['ok' => true, 'definition' => $existing];
    }

    public function definitionTenantIdByUuid(string $uuid): int
    {
        $definition = $this->definitionRepository->findByUuid(trim($uuid));
        return (int) ($definition['tenant_id'] ?? 0);
    }

    private static function sanitizeForm(array $input): array
    {
        $label = trim((string) ($input['label'] ?? ''));
        $fieldKeyInput = trim((string) ($input['field_key'] ?? ''));
        $type = strtolower(trim((string) ($input['type'] ?? 'text')));
        $sortOrderRaw = $input['sort_order'] ?? null;
        $sortOrder = is_numeric($sortOrderRaw) ? (int) $sortOrderRaw : null;

        return [
            'label' => $label,
            'field_key' => self::normalizeKey($fieldKeyInput),
            'type' => $type,
            // User custom fields are always optional in V1.
            'is_required' => 0,
            'is_filterable' => !empty($input['is_filterable']) ? 1 : 0,
            'active' => array_key_exists('active', $input) ? (int) (!empty($input['active'])) : 1,
            'sort_order' => $sortOrder !== null && $sortOrder > 0 ? $sortOrder : null,
            'options' => self::parseOptionsLines((string) ($input['options_text'] ?? '')),
            'options_text' => (string) ($input['options_text'] ?? ''),
        ];
    }

    private function validateForm(array &$form, ?int $excludeId): array
    {
        $errors = [];
        $tenantId = (int) ($form['tenant_id'] ?? 0);
        $type = (string) ($form['type'] ?? '');
        $fieldKey = (string) ($form['field_key'] ?? '');

        if ($tenantId <= 0) {
            $errors[] = t('Tenant not found');
        }
        if ($form['label'] === '') {
            $errors[] = t('Label cannot be empty');
        }
        if ($fieldKey === '') {
            $errors[] = t('Field key cannot be empty');
        }
        if (!in_array($type, self::ALLOWED_TYPES, true)) {
            $errors[] = t('Field type is invalid');
        }
        if ($fieldKey !== '') {
            $existingByKey = $this->definitionRepository->findByTenantIdAndKey($tenantId, $fieldKey);
            if ($existingByKey && (int) ($existingByKey['id'] ?? 0) !== (int) ($excludeId ?? 0)) {
                $errors[] = t('Field key already exists');
            }
        }

        if (in_array($type, ['select', 'multiselect'], true)) {
            if (!$form['options']) {
                $errors[] = t('Options are required for this field type');
            }
        } else {
            $form['is_filterable'] = in_array($type, ['boolean', 'date'], true)
                ? (int) ($form['is_filterable'] ?? 0)
                : 0;
            $form['options'] = [];
        }

        // Text/textarea types can't be used as filters — too many distinct values to enumerate in UI.
        if (!in_array($type, ['select', 'multiselect', 'boolean', 'date'], true)) {
            $form['is_filterable'] = 0;
        }

        return $errors;
    }

    // Line format: "key|Label" or just "Label" (key is derived from the label when omitted).
    // Duplicate keys are silently dropped — first occurrence wins.
    private static function parseOptionsLines(string $value): array
    {
        $lines = preg_split('/\r\n|\r|\n/', $value);
        if (!is_array($lines)) {
            return [];
        }
        $options = [];
        $sortOrder = 10;
        foreach ($lines as $line) {
            $line = trim((string) $line);
            if ($line === '') {
                continue;
            }
            $parts = explode('|', $line, 2);
            $label = trim((string) ($parts[1] ?? $parts[0]));
            $keyInput = trim((string) ($parts[1] ?? '') !== '' ? $parts[0] : $label);
            $optionKey = self::normalizeKey($keyInput);
            if ($label === '' || $optionKey === '') {
                continue;
            }
            if (isset($options[$optionKey])) {
                continue;
            }
            $options[$optionKey] = [
                'option_key' => $optionKey,
                'label' => $label,
                'active' => 1,
                'sort_order' => $sortOrder,
            ];
            $sortOrder += 10;
        }
        return array_values($options);
    }

    // Transliterates accented characters (é → e, ü → u) before slugifying so non-ASCII labels
    // produce readable keys. Falls back to direct slugification if iconv is unavailable.
    private static function normalizeKey(string $value): string
    {
        $value = trim($value);
        if ($value === '') {
            return '';
        }
        if (function_exists('iconv')) {
            $ascii = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
            if (is_string($ascii) && $ascii !== '') {
                $value = $ascii;
            }
        }
        $value = strtolower($value);
        $value = preg_replace('/[^a-z0-9]+/', '_', $value);
        $value = trim((string) $value, '_');
        return $value;
    }

    private function nextSortOrder(int $tenantId): int
    {
        $max = 0;
        foreach ($this->definitionRepository->listByTenantId($tenantId, false) as $definition) {
            $sortOrder = (int) ($definition['sort_order'] ?? 0);
            if ($sortOrder > $max) {
                $max = $sortOrder;
            }
        }
        if ($max <= 0) {
            return 100;
        }
        return $max + 10;
    }

    private function resolveFieldKey(int $tenantId, string $label, ?int $excludeId, string $preferred): string
    {
        $excludeId = (int) ($excludeId ?? 0);
        $source = trim($preferred) !== '' ? trim($preferred) : trim($label);
        $base = self::normalizeKey($source);
        if ($base === '') {
            $fallbackSeed = trim($label) !== '' ? trim($label) : $source;
            $base = 'field_' . substr(sha1($fallbackSeed), 0, 8);
        }
        if ($tenantId <= 0) {
            return '';
        }

        // Auto-increment suffix until a unique key is found; 10000 guard prevents infinite loop.
        $candidate = $base;
        $counter = 2;
        while (true) {
            $existing = $this->definitionRepository->findByTenantIdAndKey($tenantId, $candidate);
            if (!$existing || (int) ($existing['id'] ?? 0) === $excludeId) {
                return $candidate;
            }

            $candidate = $base . '_' . $counter;
            $counter++;
            if ($counter > 10000) {
                return '';
            }
        }
    }
}
