<?php
/**
 * Abstract AI Provider
 *
 * Base class for all AI providers.
 * Implements common functionality and provides helper methods.
 *
 * @package FleekDash\AI\Providers
 * @since 2.3.0
 */

namespace FleekDash\AI\Providers;

use FleekDash\AI\Core\AIProviderInterface;

defined('ABSPATH') || exit;

/**
 * Class AbstractProvider
 *
 * Base implementation for AI providers.
 */
abstract class AbstractProvider implements AIProviderInterface
{
    /**
     * Provider settings option key prefix
     *
     * @var string
     */
    protected $settingsKeyPrefix = 'fleekdash_ai_provider_';

    /**
     * Get settings option key for this provider
     *
     * @return string Option key
     */
    protected function getSettingsKey(): string
    {
        return $this->settingsKeyPrefix . $this->getId();
    }

    /**
     * Get provider settings
     *
     * @return array Current settings
     */
    public function getSettings(): array
    {
        $defaults = $this->getDefaultSettings();
        $saved = \get_option($this->getSettingsKey(), []);
        if (!\is_array($saved)) {
            $saved = [];
        }

        // Merge with defaults to ensure all keys exist
        return array_merge($defaults, $saved);
    }

    /**
     * Update provider settings
     *
     * @param array $settings Settings to update
     * @return bool True on success
     */
    public function updateSettings(array $settings): bool
    {
        $current = $this->getSettings();
        $merged = array_merge($current, $settings);
        $key = $this->getSettingsKey();

        // update_option() returns false both on failure and when the value is unchanged.
        $result = \update_option($key, $merged);
        if (!$result) {
            $stored = \get_option($key);
            if ($stored === false) {
                $result = \add_option($key, $merged, '', 'no');
            } elseif (\is_array($stored)) {
                $result = \maybe_serialize($merged) === \maybe_serialize($stored);
            }
        }

        return (bool) $result;
    }

    /**
     * Check if provider is configured and ready to use
     *
     * @return bool True if provider has valid API key
     */
    public function isConfigured(): bool
    {
        $settings = $this->getSettings();
        $api_key = $settings['api_key'] ?? '';

        return !empty($api_key) && $this->validateApiKey($api_key);
    }

    /**
     * Get provider description
     *
     * Default implementation, can be overridden
     *
     * @return string Provider description
     */
    public function getDescription(): string
    {
        return sprintf(
            'AI provider powered by %s',
            $this->getName()
        );
    }

    /**
     * Sanitize settings before saving
     *
     * @param array $settings Raw settings
     * @return array Sanitized settings
     */
    protected function sanitizeSettings(array $settings): array
    {
        $sanitized = [];

        foreach ($settings as $key => $value) {
            // API keys should be preserved as-is (but validated)
            if ($key === 'api_key') {
                $sanitized[$key] = \sanitize_text_field($value);
            } elseif (is_string($value)) {
                $sanitized[$key] = \sanitize_text_field($value);
            } elseif (is_numeric($value)) {
                $sanitized[$key] = floatval($value);
            } elseif (is_bool($value)) {
                $sanitized[$key] = (bool) $value;
            } elseif (is_array($value)) {
                $sanitized[$key] = $this->sanitizeSettings($value);
            } else {
                $sanitized[$key] = $value;
            }
        }

        return $sanitized;
    }

    /**
     * Validate settings against schema
     *
     * @param array $settings Settings to validate
     * @return array ['valid' => bool, 'errors' => array]
     */
    protected function validateSettings(array $settings): array
    {
        $errors = [];

        foreach ($this->getSettingsSchema() as $key => $field) {
            $message = $this->validationMessageForSchemaField(
                $key,
                $field,
                $settings[$key] ?? null,
            );
            if ($message !== null) {
                $errors[$key] = $message;
            }
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }

    /**
     * @param array<string, mixed> $field
     */
    private function validationMessageForSchemaField(
        string $key,
        array $field,
        mixed $value,
    ): ?string {
        $required = $field['required'] ?? false;
        $isEmpty = empty($value) && $value !== '0' && $value !== 0;
        if ($required && $isEmpty) {
            return sprintf('%s is required', $field['label'] ?? $key);
        }
        if (empty($value) && !$required) {
            return null;
        }

        return $this->validationMessageForFieldType($key, $field, $value);
    }

    /**
     * @param array<string, mixed> $field
     */
    private function validationMessageForFieldType(
        string $key,
        array $field,
        mixed $value,
    ): ?string {
        $type = $field['type'] ?? 'string';
        $label = $field['label'] ?? $key;
        $message = null;

        if ($type === 'number') {
            $message = $this->validationMessageForNumericField($key, $field, $value);
        } elseif ($type === 'email' && !\is_email((string) $value)) {
            $message = sprintf('%s must be a valid email address', $label);
        } elseif ($type === 'url' && !filter_var((string) $value, FILTER_VALIDATE_URL)) {
            $message = sprintf('%s must be a valid URL', $label);
        }

        return $message;
    }

    /**
     * @param array<string, mixed> $field
     */
    private function validationMessageForNumericField(
        string $key,
        array $field,
        mixed $value,
    ): ?string {
        $label = $field['label'] ?? $key;
        $message = null;

        if (!is_numeric($value)) {
            $message = sprintf('%s must be a number', $label);
        } else {
            $numValue = floatval($value);
            if (isset($field['min']) && $numValue < $field['min']) {
                $message = sprintf('%s must be at least %s', $label, $field['min']);
            } elseif (isset($field['max']) && $numValue > $field['max']) {
                $message = sprintf('%s must be at most %s', $label, $field['max']);
            }
        }

        return $message;
    }
}
