<?php

namespace PloverCRM\Core\Services;

use PloverCRM\Core\CRM\CRMManager;

/**
 * Custom field settings service used by the standalone app.
 */
class CustomFieldSettingsService
{
    private const STAGE_FIELD_SLUG = 'stage-plover-crm';
    private const RESERVED_PREFIX = 'plover_crm_';
    private const SUPPORTED_TYPES = [
        'text',
        'textarea',
        'email',
        'url',
        'number',
        'date',
        'date_time',
        'select',
        'radio',
        'checkbox',
    ];

    private CRMManager $crmManager;
    private string $providerId;

    public function __construct()
    {
        $this->crmManager = new CRMManager();
        $this->providerId = $this->crmManager->getSelectedProviderId();
    }

    public function buildPayload(): array
    {
        return [
            'currentProviderId' => $this->providerId,
            'currentProviderName' => $this->crmManager->getName(),
            'supportsManagement' => $this->supportsManagement(),
            'capabilities' => [
                'supportsReadonly' => $this->providerId === 'native_crm',
                'supportsHelpText' => $this->providerId === 'native_crm',
                'supportsGrouping' => $this->providerId === 'native_crm' || $this->providerId === 'fluent_crm',
                'supportsReordering' => $this->providerId === 'native_crm' || $this->providerId === 'fluent_crm',
            ],
            'fields' => $this->getFields(),
        ];
    }

    public function createField(array $payload): array
    {
        if (!$this->supportsManagement()) {
            return $this->result(false, 'Custom field management is not available for the active CRM provider.', 422);
        }

        $fields = $this->getRawFields();
        $validated = $this->validateFieldPayload($payload, false, $fields);
        if (!$validated['success']) {
            return $validated;
        }

        $fields[] = $this->buildRawField($validated['field'], count($fields));

        if (!$this->saveRawFields($fields)) {
            return $this->result(false, 'Unable to create the custom field right now.', 500);
        }

        return $this->result(true, 'Custom field created successfully.');
    }

    public function updateField(string $slug, array $payload): array
    {
        if (!$this->supportsManagement()) {
            return $this->result(false, 'Custom field management is not available for the active CRM provider.', 422);
        }

        $slug = sanitize_key($slug);
        $fields = $this->getRawFields();
        $existingIndex = $this->findFieldIndex($fields, $slug);

        if ($existingIndex === null) {
            return $this->result(false, 'The requested custom field could not be found.', 404);
        }

        $existingField = $this->normalizeField($fields[$existingIndex]);
        if (!$existingField['editable']) {
            return $this->result(false, 'System-managed fields cannot be edited from settings.', 422);
        }

        $validated = $this->validateFieldPayload($payload, true, $fields, $slug);
        if (!$validated['success']) {
            return $validated;
        }

        $fields[$existingIndex] = $this->buildRawField(
            $validated['field'],
            (int) ($fields[$existingIndex]['order'] ?? $existingIndex),
            (array) $fields[$existingIndex]
        );

        if (!$this->saveRawFields($fields)) {
            return $this->result(false, 'Unable to update the custom field right now.', 500);
        }

        return $this->result(true, 'Custom field updated successfully.');
    }

    public function deleteField(string $slug): array
    {
        if (!$this->supportsManagement()) {
            return $this->result(false, 'Custom field management is not available for the active CRM provider.', 422);
        }

        $slug = sanitize_key($slug);
        $fields = $this->getRawFields();
        $existingIndex = $this->findFieldIndex($fields, $slug);

        if ($existingIndex === null) {
            return $this->result(false, 'The requested custom field could not be found.', 404);
        }

        $existingField = $this->normalizeField($fields[$existingIndex]);
        if (!$existingField['deletable']) {
            return $this->result(false, 'System-managed fields cannot be deleted from settings.', 422);
        }

        unset($fields[$existingIndex]);

        if (!$this->saveRawFields(array_values($fields))) {
            return $this->result(false, 'Unable to delete the custom field right now.', 500);
        }

        return $this->result(true, 'Custom field deleted successfully.');
    }

    public function reorderFields(array $order): array
    {
        if (!$this->supportsManagement()) {
            return $this->result(false, 'Custom field management is not available for the active CRM provider.', 422);
        }

        $fields = $this->getRawFields();
        if (empty($fields)) {
            return $this->result(true, 'Nothing to reorder.');
        }

        $editableFields = [];
        $systemFields = [];

        foreach ($fields as $field) {
            $normalized = $this->normalizeField($field);
            if ($normalized['editable']) {
                $editableFields[$normalized['slug']] = $field;
            } else {
                $systemFields[] = $field;
            }
        }

        $orderedEditable = [];
        foreach ($order as $slug) {
            $slug = sanitize_key((string) $slug);
            if (isset($editableFields[$slug])) {
                $orderedEditable[] = $editableFields[$slug];
                unset($editableFields[$slug]);
            }
        }

        foreach ($editableFields as $remainingField) {
            $orderedEditable[] = $remainingField;
        }

        $combined = array_merge($orderedEditable, $systemFields);

        if (!$this->saveRawFields($combined)) {
            return $this->result(false, 'Unable to reorder the custom fields right now.', 500);
        }

        return $this->result(true, 'Custom fields reordered successfully.');
    }

    private function validateFieldPayload(
        array $payload,
        bool $isUpdate,
        array $existingFields,
        string $existingSlug = ''
    ): array {
        $label = sanitize_text_field((string) ($payload['label'] ?? ''));
        $slug = $this->resolveFieldSlug($payload, $label, $isUpdate, $existingSlug);
        $type = $this->normalizeCanonicalType((string) ($payload['type'] ?? 'text'));
        $group = sanitize_text_field((string) ($payload['group'] ?? ''));
        $helpText = sanitize_text_field((string) ($payload['helpText'] ?? $payload['help_text'] ?? ''));
        $readonly = !empty($payload['readonly']);
        $options = $this->sanitizeOptions($payload['options'] ?? []);

        if ($label === '') {
            return $this->result(false, 'Please provide a field label.', 422);
        }

        if ($slug === '') {
            return $this->result(false, 'Please provide a unique field slug, or use a label that can generate one.', 422);
        }

        if (!in_array($type, self::SUPPORTED_TYPES, true)) {
            return $this->result(false, 'Please choose a supported field type.', 422);
        }

        if (!$isUpdate && $this->isReservedSlug($slug)) {
            return $this->result(false, 'The chosen slug is reserved for system-managed fields.', 422);
        }

        if ($isUpdate && $existingSlug !== '' && $slug !== $existingSlug) {
            return $this->result(false, 'Field slugs cannot be changed after creation.', 422);
        }

        foreach ($existingFields as $existingField) {
            $existing = $this->normalizeField($existingField);
            if ($existing['slug'] === $slug && (!$isUpdate || $slug !== $existingSlug)) {
                return $this->result(false, 'Another field already uses that slug.', 409);
            }
        }

        if ($this->isOptionsType($type) && empty($options)) {
            return $this->result(false, 'Select, radio, and checkbox fields need at least one option.', 422);
        }

        return [
            'success' => true,
            'field' => [
                'label' => $label,
                'slug' => $slug,
                'type' => $type,
                'group' => $group,
                'help_text' => $helpText,
                'readonly' => $readonly,
                'options' => $options,
            ],
        ];
    }

    private function getFields(): array
    {
        $fields = array_map([$this, 'normalizeField'], $this->getRawFields());

        usort($fields, static function ($left, $right) {
            $leftOrder = (int) ($left['order'] ?? 0);
            $rightOrder = (int) ($right['order'] ?? 0);

            if ($leftOrder === $rightOrder) {
                return strcmp((string) ($left['label'] ?? ''), (string) ($right['label'] ?? ''));
            }

            return $leftOrder <=> $rightOrder;
        });

        return array_values($fields);
    }

    private function getRawFields(): array
    {
        $fields = [];

        if ($this->providerId === 'native_crm') {
            $fields = get_option('plover_crm_custom_fields', []);
        } elseif ($this->providerId === 'fluent_crm' && function_exists('fluentcrm_get_option')) {
            $fields = fluentcrm_get_option('contact_custom_fields', []);
        } else {
            $fields = (array) $this->crmManager->getAllCustomFields();
        }

        if (!is_array($fields)) {
            return [];
        }

        usort($fields, static function ($left, $right) {
            return ((int) ($left['order'] ?? 0)) <=> ((int) ($right['order'] ?? 0));
        });

        return array_values($fields);
    }

    private function saveRawFields(array $fields): bool
    {
        $prepared = [];

        foreach (array_values($fields) as $index => $field) {
            $field['order'] = $index;
            $prepared[] = $field;
        }

        if ($this->providerId === 'native_crm') {
            $updated = update_option('plover_crm_custom_fields', $prepared);
            if ($updated) {
                return true;
            }

            return get_option('plover_crm_custom_fields', []) === $prepared;
        }

        if ($this->providerId === 'fluent_crm' && function_exists('fluentcrm_update_option')) {
            fluentcrm_update_option('contact_custom_fields', $prepared);
            return true;
        }

        return false;
    }

    private function buildRawField(array $field, int $order, array $existingField = []): array
    {
        if ($this->providerId === 'fluent_crm') {
            $type = $this->mapCanonicalTypeToProvider($field['type']);
            $raw = $existingField;
            $raw['field_key'] = $type;
            $raw['type'] = $type;
            $raw['label'] = $field['label'];
            $raw['slug'] = $field['slug'];
            $raw['group'] = $field['group'];
            $raw['order'] = $order;

            if ($this->isOptionsType($field['type'])) {
                $raw['options'] = $field['options'];
            } else {
                unset($raw['options']);
            }

            return $raw;
        }

        return array_merge($existingField, [
            'label' => $field['label'],
            'slug' => $field['slug'],
            'type' => $field['type'],
            'group' => $field['group'],
            'help_text' => $field['help_text'],
            'readonly' => !empty($field['readonly']),
            'options' => $this->isOptionsType($field['type']) ? $field['options'] : [],
            'order' => $order,
        ]);
    }

    private function normalizeField(array $field): array
    {
        $slug = sanitize_key((string) ($field['slug'] ?? ''));
        $type = $this->normalizeCanonicalType((string) ($field['type'] ?? $field['field_key'] ?? 'text'));
        $systemManaged = $this->isSystemSlug($slug);

        return [
            'field_key' => (string) ($field['field_key'] ?? $type),
            'type' => $type,
            'label' => sanitize_text_field((string) ($field['label'] ?? '')),
            'slug' => $slug,
            'group' => sanitize_text_field((string) ($field['group'] ?? '')),
            'help_text' => sanitize_text_field((string) ($field['help_text'] ?? '')),
            'readonly' => !empty($field['readonly']),
            'options' => $this->sanitizeOptions($field['options'] ?? []),
            'order' => (int) ($field['order'] ?? 0),
            'systemManaged' => $systemManaged,
            'editable' => !$systemManaged,
            'deletable' => !$systemManaged,
        ];
    }

    private function sanitizeOptions($options): array
    {
        if (is_string($options)) {
            $options = preg_split('/\r\n|\r|\n/', $options);
        }

        if (!is_array($options)) {
            return [];
        }

        $sanitized = array_map(static function ($option) {
            return sanitize_text_field((string) $option);
        }, $options);

        $sanitized = array_values(array_filter($sanitized, static function ($option) {
            return $option !== '';
        }));

        return array_values(array_unique($sanitized));
    }

    private function resolveFieldSlug(
        array $payload,
        string $label,
        bool $isUpdate,
        string $existingSlug = ''
    ): string {
        $providedSlug = (string) ($payload['slug'] ?? '');
        if ($providedSlug !== '') {
            return sanitize_key($providedSlug);
        }

        if ($isUpdate && $existingSlug !== '') {
            return sanitize_key($existingSlug);
        }

        return $this->generateSlugFromLabel($label);
    }

    private function generateSlugFromLabel(string $label): string
    {
        if ($label === '') {
            return '';
        }

        $slug = sanitize_title($label);
        $slug = str_replace('-', '_', $slug);
        $slug = preg_replace('/_+/', '_', $slug);

        return trim((string) $slug, '_');
    }

    private function normalizeCanonicalType(string $type): string
    {
        $type = sanitize_key($type);

        return match ($type) {
            'datetime' => 'date_time',
            'select_one', 'select-one' => 'select',
            default => $type === '' ? 'text' : $type,
        };
    }

    private function mapCanonicalTypeToProvider(string $type): string
    {
        if ($this->providerId !== 'fluent_crm') {
            return $type;
        }

        return match ($type) {
            'select' => 'select-one',
            default => $type,
        };
    }

    private function isOptionsType(string $type): bool
    {
        return in_array($type, ['select', 'radio', 'checkbox'], true);
    }

    private function isReservedSlug(string $slug): bool
    {
        return $slug === self::STAGE_FIELD_SLUG
            || str_starts_with($slug, self::RESERVED_PREFIX)
            || SystemFieldRegistry::isSystemSlug($slug);
    }

    private function isSystemSlug(string $slug): bool
    {
        return SystemFieldRegistry::isSystemSlug($slug) || $this->isReservedSlug($slug);
    }

    private function findFieldIndex(array $fields, string $slug): ?int
    {
        foreach ($fields as $index => $field) {
            if (sanitize_key((string) ($field['slug'] ?? '')) === $slug) {
                return $index;
            }
        }

        return null;
    }

    private function supportsManagement(): bool
    {
        return in_array($this->providerId, ['native_crm', 'fluent_crm'], true)
            && $this->crmManager->isAvailable();
    }

    private function result(bool $success, string $message, int $status = 200): array
    {
        $payload = [
            'success' => $success,
            'message' => $message,
            'status' => $status,
        ];

        if ($success) {
            $payload['payload'] = $this->buildPayload();
        }

        return $payload;
    }
}
