<?php
/**
 * Integration Registry
 *
 * Central registry for managing integrations with security,
 * backup, and rollback capabilities.
 *
 * @package FleekDash\Integration
 * @since 2.3.0
 *
 * SECURITY FEATURES:
 * - Manifest validation (XSS prevention)
 * - Conflict detection
 * - Rollback capability
 * - Backup before changes
 */

namespace FleekDash\Integration;

defined('ABSPATH') || exit;

// Load debug helpers
if (!function_exists('fleekdash_log_info')) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/debug-helpers.php';
}

class Registry
{
    /**
     * Backup directory
     *
     * @var string
     */
    const BACKUP_DIR = FLEEKDASH_PLUGIN_DIR . 'backups/integrations/';
    const BACKUP_EXTENSION = '.json';

    /**
     * Register new integration
     *
     * @param array $manifest Integration manifest
     * @return array Result with success status and data
     */
    public static function register(array $manifest): array // NOSONAR php:S1142 - Registration performs validate/backup/insert/route rollback with explicit exits.
    {
        global $wpdb;

        // ===============================================================
        // STEP 1: VALIDATE MANIFEST (XSS Prevention)
        // ===============================================================

        $validation = ManifestValidator::validate($manifest);

        if (!$validation['success']) {
            return [
                'success' => false,
                'errors' => $validation['errors'],
            ];
        }

        $validated_manifest = $validation['manifest'];
        $manifest_hash = $validation['hash'];

        // ===============================================================
        // STEP 2: CHECK CONFLICTS
        // ===============================================================

        $conflicts = self::detectConflicts($validated_manifest);

        if (!empty($conflicts)) {
            return [
                'success' => false,
                'errors' => ['Conflicts detected: ' . implode(', ', $conflicts)],
                'conflicts' => $conflicts,
            ];
        }

        // ===============================================================
        // STEP 3: BACKUP CURRENT STATE (Before Changes)
        // ===============================================================

        $backup_id = self::createBackup($validated_manifest['integration_id'], 'pre_registration');

        // ===============================================================
        // STEP 4: INSERT OR UPDATE REGISTRY (idempotent — avoids duplicate-key races on sync)
        // ===============================================================

        $table = $wpdb->prefix . 'fleekdash_integration_registry';
        $persist = RegistryPersistence::persistRegistryRow($table, $validated_manifest, $manifest_hash);

        if (!$persist['success']) {
            self::restoreBackupIfAvailable($backup_id);

            return [
                'success' => false,
                'errors' => ['Database persistence failed: ' . ($persist['error'] ?? $wpdb->last_error)],
            ];
        }

        // ===============================================================
        // STEP 5: REGISTER ROUTES (if present)
        // ===============================================================

        if (!empty($validated_manifest['routes'])) {
            $route_result = self::registerRoutes(
                $validated_manifest['integration_id'],
                $validated_manifest['routes']
            );

            if (!$route_result['success']) {
                if ($persist['inserted_new_row']) {
                    self::unregister($validated_manifest['integration_id']);
                    self::restoreBackupIfAvailable($backup_id);
                }

                return $route_result;
            }
        }

        // ===============================================================
        // STEP 6: LOG SUCCESS
        // ===============================================================

        fleekdash_log_info(sprintf(
            '[FleekDash Integration] Registered: %s v%s (Type: %s, Backup: %s)',
            $validated_manifest['name'],
            $validated_manifest['version'],
            $validated_manifest['integration_type'],
            $backup_id
        ));
        self::invalidateIntegrationCaches();

        return [
            'success' => true,
            'integration_id' => $validated_manifest['integration_id'],
            'backup_id' => $backup_id,
        ];
    }

    /**
     * Unregister integration
     *
     * @param string $integration_id Integration ID
     * @return array Result
     */
    public static function unregister(string $integration_id): array
    {
        global $wpdb;

        // Sanitize input
        $integration_id = ManifestValidator::sanitizeIntegrationId($integration_id);

        // Create backup before deletion
        $backup_id = self::createBackup($integration_id, 'pre_unregistration');

        // Delete from database (cascade will delete routes and settings)
        $table = $wpdb->prefix . 'fleekdash_integration_registry';

        $result = $wpdb->delete(
            $table,
            ['integration_id' => $integration_id],
            ['%s']
        );

        if ($result === false) {
            return [
                'success' => false,
                'errors' => ['Failed to unregister integration'],
            ];
        }

        fleekdash_log_info(sprintf(
            '[FleekDash Integration] Unregistered: %s (Backup: %s)',
            $integration_id,
            $backup_id
        ));
        self::invalidateIntegrationCaches();

        return [
            'success' => true,
            'backup_id' => $backup_id,
        ];
    }

    /**
     * Get all integrations
     *
     * @param bool $active_only Only active integrations
     * @return array List of integrations
     */
    public static function getAll(bool $active_only = false): array
    {
        global $wpdb;

        $table = $wpdb->prefix . 'fleekdash_integration_registry';

        $sql = "SELECT * FROM `{$table}`";

        if ($active_only) {
            $sql .= " WHERE `is_active` = 1 AND `is_available` = 1";
        }

        $sql .= " ORDER BY `name` ASC";

        $results = $wpdb->get_results($sql, ARRAY_A);

        // Decode manifest JSON
        foreach ($results as &$integration) {
            $integration['manifest'] = json_decode($integration['manifest'], true);
            $integration['settings_schema'] = !empty($integration['settings_schema'])
                ? json_decode($integration['settings_schema'], true)
                : ($integration['manifest']['settings_schema'] ?? null);
            $integration['allowed_roles'] = !empty($integration['allowed_roles'])
                ? json_decode($integration['allowed_roles'], true)
                : ($integration['manifest']['allowed_roles'] ?? null);
            $integration['is_active'] = isset($integration['is_active']) ? (bool) $integration['is_active'] : false;
            $integration['is_available'] = isset($integration['is_available']) ? (bool) $integration['is_available'] : true;
            $integration['license_required'] = isset($integration['license_required']) ? (bool) $integration['license_required'] : false;
        }

        return $results;
    }

    /**
     * Get single integration
     *
     * @param string $integration_id Integration ID
     * @return array|null Integration data or null
     */
    public static function get(string $integration_id): ?array
    {
        global $wpdb;

        $integration_id = \sanitize_text_field($integration_id);
        $table = $wpdb->prefix . 'fleekdash_integration_registry';

        $integration = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM `{$table}` WHERE `integration_id` = %s",
                $integration_id
            ),
            ARRAY_A
        );

        if (!$integration) {
            return null;
        }

        $integration['manifest'] = json_decode($integration['manifest'], true);
        $integration['settings_schema'] = !empty($integration['settings_schema'])
            ? json_decode($integration['settings_schema'], true)
            : ($integration['manifest']['settings_schema'] ?? null);
        $integration['allowed_roles'] = !empty($integration['allowed_roles'])
            ? json_decode($integration['allowed_roles'], true)
            : ($integration['manifest']['allowed_roles'] ?? null);
        $integration['is_active'] = isset($integration['is_active']) ? (bool) $integration['is_active'] : false;
        $integration['is_available'] = isset($integration['is_available']) ? (bool) $integration['is_available'] : true;
        $integration['license_required'] = isset($integration['license_required']) ? (bool) $integration['license_required'] : false;

        return $integration;
    }

    /**
     * Activate integration
     *
     * @param string $integration_id Integration ID
     * @return array Result
     */
    public static function activate(string $integration_id): array
    {
        $result = self::updateStatus($integration_id, true);
        if ($result['success']) {
            self::syncToLegacyOption($integration_id, true);
            self::invalidateIntegrationCaches();
        }
        return $result;
    }

    /**
     * Deactivate integration
     *
     * @param string $integration_id Integration ID
     * @return array Result
     */
    public static function deactivate(string $integration_id): array
    {
        $result = self::updateStatus($integration_id, false);
        if ($result['success']) {
            self::syncToLegacyOption($integration_id, false);
            self::invalidateIntegrationCaches();
        }
        return $result;
    }

    /**
     * Update integration status in DB
     *
     * @param string $integration_id Integration ID
     * @param bool $active Active status
     * @return array Result
     */
    private static function updateStatus(string $integration_id, bool $active): array
    {
        global $wpdb;

        $integration_id = \sanitize_text_field($integration_id);
        $table = $wpdb->prefix . 'fleekdash_integration_registry';

        $result = $wpdb->update(
            $table,
            ['is_active' => $active ? 1 : 0],
            ['integration_id' => $integration_id],
            ['%d'],
            ['%s']
        );

        if ($result === false) {
            return [
                'success' => false,
                'errors' => ['Failed to update status'],
            ];
        }

        return ['success' => true];
    }


    /**
     * Sync integration status to legacy fleekdash_active_integrations option
     *
     * This ensures V2.2 and V2.3 systems stay in sync and integrations
     * remain active after page reload.
     *
     * @param string $integration_id Integration ID
     * @param bool $active Active status
     * @return void
     */
    private static function syncToLegacyOption(string $integration_id, bool $active): void // NOSONAR php:S3776 - Legacy option compatibility handles categorized and flat formats.
    {
        $option_name = 'fleekdash_active_integrations';
        $active_integrations = get_option($option_name, []);

        if (!is_array($active_integrations)) {
            $active_integrations = [];
        }

        $integration = self::get($integration_id);
        $category = $integration['integration_type'] ?? ($integration['category'] ?? 'api');

        // Handle categorized format (V2.2)
        $categories = ['api', 'app', 'plugin', 'theme', 'native', 'standalone', 'hybrid'];
        $is_categorized = false;
        foreach ($categories as $cat) {
            if (isset($active_integrations[$cat])) {
                $is_categorized = true;
                break;
            }
        }

        if ($active) {
            if ($is_categorized) {
                if (!isset($active_integrations[$category])) {
                    $active_integrations[$category] = [];
                }
                if (!in_array($integration_id, $active_integrations[$category], true)) {
                    $active_integrations[$category][] = $integration_id;
                }
            } else {
                if (!in_array($integration_id, $active_integrations, true)) {
                    $active_integrations[] = $integration_id;
                }
            }
        } else {
            if ($is_categorized) {
                foreach ($active_integrations as $cat => $list) {
                    if (is_array($list)) {
                        $active_integrations[$cat] = array_values(array_diff($list, [$integration_id]));
                        if (empty($active_integrations[$cat])) {
                            unset($active_integrations[$cat]);
                        }
                    }
                }
            } else {
                $active_integrations = array_values(array_diff($active_integrations, [$integration_id]));
            }
        }

        update_option($option_name, $active_integrations);
        self::syncToV3ActiveOption($integration_id, $active);

        // Clear caches to force refresh
        if (class_exists('\FleekDash\Integrations\Core\Container')) {
            try {
                $container = \FleekDash\Integrations\Core\Container::getInstance();
                $cache = $container->getCache();
                if ($cache) {
                    $cache->invalidateAll();
                }
            } catch (\Exception $e) {
                // Ignore cache invalidation errors
            }
        }
    }

    /**
     * Keep filesystem integrations loadable after Registry-driven toggles.
     */
    private static function syncToV3ActiveOption(string $integration_id, bool $active): void
    {
        $active_integrations = \get_option('fleekdash_v3_active', []);
        if (!is_array($active_integrations)) {
            $active_integrations = [];
        }

        if ($active && !in_array($integration_id, $active_integrations, true)) {
            $active_integrations[] = $integration_id;
        }

        if (!$active) {
            $active_integrations = array_values(array_diff($active_integrations, [$integration_id]));
        }

        \update_option('fleekdash_v3_active', $active_integrations);
        \delete_transient('fleekdash_integrations_cache_v3');
    }

    private static function invalidateIntegrationCaches(): void
    {
        if (class_exists('\FleekDash\Services\IntegrationMergerService')) {
            \FleekDash\Services\IntegrationMergerService::clearCache();
        }
    }

    /**
     * Register routes for integration
     *
     * @param string $integration_id Integration ID
     * @param array $routes Routes to register
     * @return array Result
     */
    private static function registerRoutes(string $integration_id, array $routes): array
    {
        global $wpdb;

        $table = $wpdb->prefix . 'fleekdash_integration_routes';

        foreach ($routes as $route) {
            $route_path = $route['path'];
            $route_row = [
                'route_title' => $route['title'],
                'component_url' => $route['component_url'],
                'icon' => $route['icon'] ?? 'box',
                'permissions' => \wp_json_encode($route['permissions'] ?? ['authenticated']),
                'sandbox_mode' => $route['sandbox_mode'] ?? 'strict',
                'is_active' => 1,
            ];
            $route_formats = ['%s', '%s', '%s', '%s', '%s', '%d'];

            $existing_id = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT `id` FROM `{$table}` WHERE `integration_id` = %s AND `route_path` = %s",
                    $integration_id,
                    $route_path
                )
            );

            if ($existing_id) {
                $result = $wpdb->update(
                    $table,
                    $route_row,
                    ['id' => (int) $existing_id],
                    $route_formats,
                    ['%d']
                );
            } else {
                $result = $wpdb->insert(
                    $table,
                    array_merge(
                        [
                            'integration_id' => $integration_id,
                            'route_path' => $route_path,
                        ],
                        $route_row
                    ),
                    array_merge(['%s', '%s'], $route_formats)
                );
            }

            if ($result === false) {
                return [
                    'success' => false,
                    'errors' => ['Failed to register route: ' . $wpdb->last_error],
                ];
            }
        }

        if (class_exists(RouteManager::class)) {
            RouteManager::clearCache();
        }

        return ['success' => true];
    }

    /**
     * Detect conflicts
     *
     * @param array $manifest Validated manifest
     * @return array List of conflicts
     */
    private static function detectConflicts(array $manifest): array
    {
        $conflicts = [];

        // Check WordPress menu slug conflicts
        if (!empty($manifest['wp_menu_slug']) && self::wpMenuSlugExists(
            $manifest['wp_menu_slug'],
            $manifest['integration_id']
        )) {
            $conflicts[] = 'WordPress menu slug already in use';
        }

        return $conflicts;
    }

    /**
     * Check if WordPress menu slug exists
     *
     * @param string $slug Menu slug
     * @return bool True if exists
     */
    private static function wpMenuSlugExists(string $slug, string $exclude_integration_id = ''): bool
    {
        global $wpdb;

        $table = $wpdb->prefix . 'fleekdash_integration_registry';

        if ($exclude_integration_id !== '') {
            $count = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT COUNT(*) FROM `{$table}` WHERE `wp_menu_slug` = %s AND `integration_id` != %s",
                    $slug,
                    $exclude_integration_id
                )
            );
        } else {
            $count = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT COUNT(*) FROM `{$table}` WHERE `wp_menu_slug` = %s",
                    $slug
                )
            );
        }

        return (int) $count > 0;
    }

    /**
     * Create backup before changes
     *
     * SECURITY FIX #4: Migration Backup
     *
     * @param string $integration_id Integration ID
     * @param string $reason Backup reason
     * @return string Backup ID
     */
    private static function createBackup(string $integration_id, string $reason): string
    {
        // Create backup directory if doesn't exist
        if (!is_dir(self::BACKUP_DIR)) {
            wp_mkdir_p(self::BACKUP_DIR);
        }

        $backup_id = sprintf(
            '%s_%s_%s',
            $integration_id,
            $reason,
            gmdate('Y-m-d_His')
        );

        $backup_file = self::BACKUP_DIR . $backup_id . self::BACKUP_EXTENSION;

        // Get current state
        $existing_integration = self::get($integration_id);
        $current_state = [
            'integration' => $existing_integration,
            'backup_id' => $backup_id,
            'reason' => $reason,
            'timestamp' => \current_time('mysql'),
        ];

        $should_persist_backup = !(
            $reason === 'pre_registration'
            && $existing_integration === null
        );

        // Skip writing empty pre-registration backups when there is no previous state.
        if ($should_persist_backup) {
            file_put_contents(
                $backup_file,
                \wp_json_encode($current_state, JSON_PRETTY_PRINT)
            );
        }

        return $backup_id;
    }

    private static function restoreBackupIfAvailable(string $backup_id): void
    {
        $backup_file = self::BACKUP_DIR . $backup_id . self::BACKUP_EXTENSION;
        if (file_exists($backup_file)) {
            self::restoreBackup($backup_id);
        }
    }

    /**
     * Restore from backup
     *
     * SECURITY FIX #5: Rollback Capability
     *
     * @param string $backup_id Backup ID
     * @return array Result
     */
    private static function restoreBackup(string $backup_id): array
    {
        $backup_file = self::BACKUP_DIR . $backup_id . self::BACKUP_EXTENSION;

        if (!file_exists($backup_file)) {
            return [
                'success' => false,
                'errors' => ['Backup not found'],
            ];
        }

        $backup_data = json_decode(file_get_contents($backup_file), true);

        if (!$backup_data || !isset($backup_data['integration'])) {
            return [
                'success' => false,
                'errors' => ['Invalid backup data'],
            ];
        }

        // Restore integration
        $result = ['success' => true];
        if ($backup_data['integration']) {
            $manifest = $backup_data['integration']['manifest'];
            $result = self::register($manifest);
        }

        return $result;
    }

    /**
     * List all backups
     *
     * @param string|null $integration_id Filter by integration ID
     * @return array List of backups
     */
    public static function listBackups(?string $integration_id = null): array
    {
        if (!is_dir(self::BACKUP_DIR)) {
            return [];
        }

        $backups = [];
        $files = glob(self::BACKUP_DIR . '*' . self::BACKUP_EXTENSION);

        foreach ($files as $file) {
            $backup_id = basename($file, self::BACKUP_EXTENSION);

            // Filter by integration_id if provided
            if ($integration_id && strpos($backup_id, $integration_id) !== 0) {
                continue;
            }

            $data = json_decode(file_get_contents($file), true);

            $backups[] = [
                'backup_id' => $backup_id,
                'integration_id' => $data['integration']['integration_id'] ?? 'unknown',
                'reason' => $data['reason'] ?? 'unknown',
                'timestamp' => $data['timestamp'] ?? 'unknown',
                'file' => $file,
            ];
        }

        // Sort by timestamp (newest first)
        usort($backups, function ($a, $b) {
            return strcmp($b['timestamp'], $a['timestamp']);
        });

        return $backups;
    }
}
