<?php

declare(strict_types=1);

namespace MailerPress\Core\Webhooks;

\defined('ABSPATH') || exit;

use MailerPress\Core\Webhooks\Events\WebhookEventInterface;
use MailerPress\Core\Webhooks\Events\WebhookEventRegistry;

/**
 * Gestionnaire principal des webhooks
 * 
 * Orchestre la réception et l'envoi de webhooks
 * 
 * @since 1.2.0
 */
class WebhookManager
{
    private WebhookDispatcher $dispatcher;
    private WebhookEventRegistry $eventRegistry;
    private array $webhookConfigs = [];
    private static ?WebhookManager $instance = null;

    public function __construct(?WebhookDispatcher $dispatcher = null)
    {
        $this->dispatcher = $dispatcher ?? new WebhookDispatcher();
        $this->eventRegistry = new WebhookEventRegistry();
        $this->loadWebhookConfigs();
    }

    /**
     * Instance singleton
     * 
     * @return self
     */
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Vérifie si MailerPress Pro est actif
     * 
     * @return bool True si Pro est actif
     */
    private function isProActive(): bool
    {
        if (!function_exists('is_plugin_active')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        return function_exists('is_plugin_active')
            && is_plugin_active('mailerpress-pro/mailerpress-pro.php');
    }

    /**
     * Enregistre un événement personnalisé
     * 
     * @param string $key Clé unique de l'événement
     * @param string $class Classe de l'événement (doit implémenter WebhookEventInterface)
     * @return void
     */
    public function registerEvent(string $key, string $class): void
    {
        $this->eventRegistry->register($key, $class);
    }

    /**
     * Crée et envoie un webhook
     * 
     * @param string $eventKey Clé de l'événement
     * @param array $data Données de l'événement
     * @param array $options Options d'envoi (URL, secret, etc.)
     * @return array|null Réponse WordPress ou null en cas d'erreur
     */
    public function sendWebhook(string $eventKey, array $data = [], array $options = []): ?array
    {
        $event = $this->eventRegistry->create($eventKey, $data);

        if (!$event) {
            return null;
        }

        $url = $options['url'] ?? null;

        if (empty($url)) {
            return null;
        }

        try {
            return $this->dispatcher->dispatch($url, $event, $options);
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * Envoie un webhook vers plusieurs URLs
     * 
     * @param string $eventKey Clé de l'événement
     * @param array $data Données de l'événement
     * @param array $urls Liste des URLs
     * @param array $options Options d'envoi
     * @return array
     */
    public function sendWebhookMultiple(string $eventKey, array $data = [], array $urls = [], array $options = []): array
    {
        $event = $this->eventRegistry->create($eventKey, $data);

        if (!$event) {
            return [];
        }

        try {
            $responses = $this->dispatcher->dispatchMultiple($urls, $event, $options);return $responses;
        } catch (\Exception $e) {
            return [];
        }
    }

    /**
     * Enregistre une configuration de webhook entrant
     * 
     * @param string $webhookId Identifiant unique du webhook
     * @param array $config Configuration (secret, etc.)
     * @return void
     */
    public function registerWebhookConfig(string $webhookId, array $config = []): void
    {
        // Préserver la configuration existante si elle existe
        $existingConfig = $this->webhookConfigs[$webhookId] ?? [];

        // Fusionner avec les valeurs par défaut, puis avec l'existant, puis avec la nouvelle config
        // Cela garantit que le secret existant est préservé si non fourni dans la nouvelle config
        // Mais si le secret est fourni dans $config, il sera utilisé
        $mergedConfig = array_merge([
            'id' => $webhookId,
            'secret' => '',
            'enabled' => true,
        ], $existingConfig, $config);

        // S'assurer que le secret de la nouvelle config est utilisé si fourni
        if (isset($config['secret']) && !empty($config['secret'])) {
            $mergedConfig['secret'] = $config['secret'];
        }

        $this->webhookConfigs[$webhookId] = $mergedConfig;

        $this->saveWebhookConfigs();
    }

    /**
     * Récupère la configuration d'un webhook
     * 
     * @param string $webhookId
     * @return array|null
     */
    public function getWebhookConfig(string $webhookId): ?array
    {
        return $this->webhookConfigs[$webhookId] ?? null;
    }

    /**
     * Récupère toutes les configurations de webhooks
     * 
     * @return array
     */
    public function getAllWebhookConfigs(): array
    {
        return $this->webhookConfigs;
    }

    /**
     * Supprime une configuration de webhook
     * 
     * @param string $webhookId
     * @return void
     */
    public function deleteWebhookConfig(string $webhookId): void
    {
        if (isset($this->webhookConfigs[$webhookId])) {
            unset($this->webhookConfigs[$webhookId]);
            $this->saveWebhookConfigs();
        }
    }

    /**
     * Charge les configurations depuis la base de données
     * 
     * @return void
     */
    private function loadWebhookConfigs(): void
    {
        $configs = get_option('mailerpress_webhook_configs', []);

        if (is_array($configs)) {
            $this->webhookConfigs = $configs;
        }
    }

    /**
     * Sauvegarde les configurations dans la base de données
     * 
     * @return void
     */
    private function saveWebhookConfigs(): void
    {
        $result = update_option('mailerpress_webhook_configs', $this->webhookConfigs);
        // Vérifier immédiatement après la sauvegarde
        $saved = get_option('mailerpress_webhook_configs', []);
    }

    /**
     * Retourne le registre d'événements
     * 
     * @return WebhookEventRegistry
     */
    public function getEventRegistry(): WebhookEventRegistry
    {
        return $this->eventRegistry;
    }

    /**
     * Retourne le dispatcher
     * 
     * @return WebhookDispatcher
     */
    public function getDispatcher(): WebhookDispatcher
    {
        return $this->dispatcher;
    }

    /**
     * Enregistre une configuration de webhook sortant (outgoing)
     * 
     * @param string $eventKey Clé de l'événement
     * @param array $config Configuration (urls, secret, enabled)
     * @return void
     */
    public function registerOutgoingWebhookConfig(string $eventKey, array $config = []): void
    {
        $optionValue = get_option('mailerpress_outgoing_webhook_configs', []);

        // L'option peut être stockée en JSON string (via ApiService.createOption) ou directement en array
        $outgoingConfigs = [];
        if (is_string($optionValue)) {
            $decoded = json_decode($optionValue, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $outgoingConfigs = $decoded;
            } else {
                $outgoingConfigs = [];
            }
        } elseif (is_array($optionValue)) {
            $outgoingConfigs = $optionValue;
        }

        // Préserver les valeurs existantes si elles ne sont pas fournies
        $existingConfig = $outgoingConfigs[$eventKey] ?? [];

        // Construire la nouvelle configuration
        $newConfig = [
            'enabled' => isset($config['enabled']) ? (bool) $config['enabled'] : ($existingConfig['enabled'] ?? false),
            'urls' => isset($config['urls']) && is_array($config['urls']) ? $config['urls'] : ($existingConfig['urls'] ?? []),
            'secret' => isset($config['secret']) && !empty($config['secret']) ? $config['secret'] : ($existingConfig['secret'] ?? ''),
        ];

        $outgoingConfigs[$eventKey] = $newConfig;

        $result = update_option('mailerpress_outgoing_webhook_configs', $outgoingConfigs);

        // Vérifier immédiatement après la sauvegarde
        $verifyValue = get_option('mailerpress_outgoing_webhook_configs', []);

        // Décode si c'est une string JSON
        $verify = [];
        if (is_string($verifyValue)) {
            $decoded = json_decode($verifyValue, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $verify = $decoded;
            }
        } elseif (is_array($verifyValue)) {
            $verify = $verifyValue;
        }
    }

    /**
     * Récupère la configuration d'un webhook sortant
     * 
     * @param string $eventKey
     * @return array|null
     */
    public function getOutgoingWebhookConfig(string $eventKey): ?array
    {
        $optionValue = get_option('mailerpress_outgoing_webhook_configs', []);

        // L'option peut être stockée en JSON string (via ApiService.createOption) ou directement en array
        $outgoingConfigs = [];
        if (is_string($optionValue)) {
            $decoded = json_decode($optionValue, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $outgoingConfigs = $decoded;
            }
        } elseif (is_array($optionValue)) {
            $outgoingConfigs = $optionValue;
        }
        $config = $outgoingConfigs[$eventKey] ?? null;

        return $config;
    }

    /**
     * Récupère toutes les configurations de webhooks sortants
     * 
     * @return array
     */
    public function getAllOutgoingWebhookConfigs(): array
    {
        $optionValue = get_option('mailerpress_outgoing_webhook_configs', []);

        // L'option peut être stockée en JSON string (via ApiService.createOption) ou directement en array
        $configs = [];
        if (is_string($optionValue)) {
            $decoded = json_decode($optionValue, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $configs = $decoded;
            }
        } elseif (is_array($optionValue)) {
            $configs = $optionValue;
        }

        // Normaliser les configurations pour s'assurer qu'elles ont la bonne structure
        foreach ($configs as $key => &$config) {
            if (!is_array($config)) {
                unset($configs[$key]);
                continue;
            }

            // S'assurer que toutes les clés nécessaires sont présentes
            $config = array_merge([
                'enabled' => false,
                'urls' => [],
                'secret' => '',
            ], $config);
        }

        return $configs;
    }

    /**
     * Envoie automatiquement un webhook si configuré
     * 
     * @param string $eventKey Clé de l'événement
     * @param array $data Données de l'événement
     * @return void
     */
    public function triggerOutgoingWebhook(string $eventKey, array $data = []): void
    {
        // Vérifier que Pro est actif
        if (!$this->isProActive()) {
            return;
        }

        $config = $this->getOutgoingWebhookConfig($eventKey);
        if (!$config) {
            return;
        }

        if (empty($config['enabled'])) {
            return;
        }

        if (empty($config['urls'])) {
            return;
        }

        $urls = is_array($config['urls']) ? $config['urls'] : [$config['urls']];
        $urls = array_filter($urls); // Enlever les URLs vides

        if (empty($urls)) {
            return;
        }

        // Récupérer le secret global (obligatoire pour la sécurité)
        $globalSecret = get_option('mailerpress_outgoing_webhook_secret', '');

        if (empty($globalSecret)) {
            return;
        }

        $options = [
            'secret' => $globalSecret,
        ];

        $this->sendWebhookMultiple($eventKey, $data, $urls, $options);
    }
}
