<?php

if (! defined('BASEPATH')) {
    exit('No direct script access allowed');
}

/**
 * JCOGS Image Pro - Installer/Updater
 * ====================================
 * Native EE7 installation and update system with Pro database schema
 * 
 * @category   ExpressionEngine Add-on
 * @package    JCOGS Image Pro
 * @author     JCOGS Design <contact@jcogs.net>
 * @copyright  Copyright (c) 2021 - 2025 JCOGS Design
 * @license    https://jcogs.net/add-ons/license/jcogs_img_pro
 * @version    2.0.6
 * @link       https://JCOGS.net/
 * @since      2.0.0
 */

use ExpressionEngine\Service\Addon\Installer;

class Jcogs_img_pro_upd extends Installer
{
    public $has_cp_backend = 'y';
    public $has_publish_fields = 'n';

    /**
     * Log debug message during installation/update
     * Uses direct log_message since addon services may not be available
     * 
     * @param string $message Message to log
     * @return void
     */
    private function log_debug($message)
    {
        if (function_exists('log_message')) {
            log_message('debug', "[JCOGS Image Pro] {$message}");
        }
    }

    /**
     * Resolve a language line for installer diagnostics with a safe fallback.
     */
    private function installer_lang_line(string $key, string $fallback): string
    {
        if (!function_exists('ee')) {
            return $fallback;
        }

        $ee = ee();
        if (!is_object($ee) || !isset($ee->lang) || !is_object($ee->lang)) {
            return $fallback;
        }

        try {
            $ee->lang->loadfile('jcogs_img_pro');
            $line = $ee->lang->line($key);

            return is_string($line) && $line !== '' ? $line : $fallback;
        } catch (\Throwable $e) {
            return $fallback;
        }
    }

    /**
     * Validate GD runtime compatibility using Imagine's native GD driver checks.
     *
     * @param bool $strict Whether to throw when unsupported (install should be strict)
     */
    private function validate_imagine_gd_runtime(bool $strict): bool
    {
        try {
            $autoload_path = defined('PATH_THIRD')
                ? PATH_THIRD . 'jcogs_img_pro/vendor/autoload.php'
                : __DIR__ . '/vendor/autoload.php';

            if (!class_exists('\\Imagine\\Gd\\DriverInfo', false) && is_file($autoload_path)) {
                require_once $autoload_path;
            }

            if (!class_exists('\\Imagine\\Gd\\DriverInfo')) {
                throw new \RuntimeException('Unable to load Imagine GD driver metadata');
            }

            $driver_info = \Imagine\Gd\DriverInfo::get(true);
            $driver_info->checkVersionIsSupported();

            return true;
        } catch (\Imagine\Exception\NotSupportedException $e) {
            $gd_version = defined('GD_VERSION') ? (string) GD_VERSION : 'unknown';
            $gd_info = extension_loaded('gd') ? gd_info() : [];
            $gd_info_version = (isset($gd_info['GD Version']) && is_string($gd_info['GD Version']))
                ? $gd_info['GD Version']
                : 'unknown';

            $template = $this->installer_lang_line(
                'gd_runtime_not_supported_installer',
                'Image Pro requires GD2 2.0.1+ runtime support. Install/upgrade check failed: %s (GD_VERSION=%s, gd_info=%s)'
            );

            $message = sprintf(
                $template,
                $e->getMessage(),
                $gd_version,
                $gd_info_version
            );

            $this->log_debug($message);

            if ($strict) {
                throw new \RuntimeException($message, 0, $e);
            }

            return false;
        } catch (\Throwable $e) {
            $this->log_debug('GD runtime preflight check error: ' . $e->getMessage());

            if ($strict) {
                throw new \RuntimeException('Unable to validate GD runtime compatibility for Image Pro install: ' . $e->getMessage(), 0, $e);
            }

            return false;
        }
    }

    public function install()
    {
        // Block installation early when runtime fails Imagine GD support checks.
        $this->validate_imagine_gd_runtime(true);

        // Run parent installation (handles migrations)
        parent::install();

        // Remove Windows-only HEIC artifacts on non-Windows hosts.
        $this->prune_windows_heic_binaries_on_non_windows();
        
        // Ensure cp_js_end extension is registered.
        $this->ensure_cp_js_end_extension_registered();

        // Initialize settings with defaults
        $this->initialize_settings();

        // Attempt legacy migration if legacy addon exists
        $this->migrate_from_legacy();

        // Clear EE caches for safety
        $this->clear_caches();

        // Clear Jump Menu cache to insert new menu items
        ee('CP/JumpMenu')->clearAllCaches();

        return true;
    }

    public function update($current = '')
    {
        if ($current == JCOGS_IMG_PRO_VERSION) {
            $this->ensure_cp_js_end_extension_registered();
            return false;
        }

        // Update path should not hard-block existing installs, but should be explicit in logs.
        if (!$this->validate_imagine_gd_runtime(false)) {
            $this->log_debug('Update continuing despite GD runtime preflight failure.');
        }
        
        // Run parent update (handles migrations)
        parent::update($current);

        // Remove Windows-only HEIC artifacts on non-Windows hosts.
        $this->prune_windows_heic_binaries_on_non_windows();
        
        // Ensure cp_js_end extension is present after migrations.
        $this->ensure_cp_js_end_extension_registered();

        // Update version metadata from addon constants
        $this->update_version_metadata();

        // Update cache count for performance optimization
        $this->update_cache_performance_settings();

        // Reconcile legacy unread error notification backlog for pre-2.0.4 upgrades.
        $this->reconcile_error_notification_state_for_204($current);

        // Clear caches after update
        $this->clear_caches();

        // Clear Jump Menu cache
        ee('CP/JumpMenu')->clearAllCaches();

        return true;
    }

    /**
     * Ensure cp_js_end hook is registered for this add-on extension class.
     */
    private function ensure_cp_js_end_extension_registered(): void
    {
        try {
            $addon = ee('Addon')->get('jcogs_img_pro');
            if (!$addon) {
                return;
            }

            $class = $addon->getExtensionClass();

            $exists = ee('Model')->get('Extension')
                ->filter('class', $class)
                ->filter('hook', 'cp_js_end')
                ->filter('method', 'cp_js_end')
                ->count();

            if ($exists > 0) {
                return;
            }

            $ext = [
                'class' => $class,
                'method' => 'cp_js_end',
                'hook' => 'cp_js_end',
                'settings' => serialize([]),
                'priority' => 10,
                'version' => JCOGS_IMG_PRO_VERSION,
                'enabled' => 'y',
            ];

            ee('Model')->make('Extension', $ext)->save();
        } catch (\Throwable $e) {
            // Silent fail - best effort reconciliation.
        }
    }

    public function uninstall()
    {
        // Run parent uninstall (handles migrations rollback)
        parent::uninstall();

        // Clear EE cache
        ee()->cache->delete('/jcogs_img_pro/');

        return true;
    }

    /**
     * One-time reconciliation for 2.0.4+ upgrades.
     *
     * Older installations may have accumulated large unread error backlogs.
     * Marking historical unread entries as read prevents stale CP prompts from
     * persisting after upgrade.
     */
    private function reconcile_error_notification_state_for_204(string $current): void
    {
        try {
            if ($current === '' || !version_compare($current, '2.0.4', '<')) {
                return;
            }

            if (!ee()->db->table_exists('jcogs_img_pro_error_notifications')) {
                return;
            }

            $site_id = (int) ee()->config->item('site_id');

            $unread_count = (int) ee()->db->where('site_id', $site_id)
                ->where('is_read', 0)
                ->count_all_results('jcogs_img_pro_error_notifications');

            if ($unread_count < 1) {
                return;
            }

            ee()->db->where('site_id', $site_id)
                ->where('is_read', 0)
                ->update('jcogs_img_pro_error_notifications', ['is_read' => 1]);

            $this->log_debug("Marked {$unread_count} historical unread error notifications as read during 2.0.4 reconciliation");
        } catch (\Throwable $e) {
            // Best-effort only; do not block add-on updates.
            $this->log_debug('2.0.4 error notification reconciliation failed: ' . $e->getMessage());
        }
    }

    /**
     * Migrate settings and data from legacy JCOGS Image addon
     * Only runs if legacy addon exists and has valid license
     * 
     * @return void
     */
    private function migrate_from_legacy(): void
    {
        try {
            // Get legacy settings once and pass to all migration methods
            $legacy_settings = $this->get_legacy_settings();
            
            // Check if legacy addon exists and is valid
            if (!$this->is_legacy_addon_valid($legacy_settings)) {
                $this->log_debug('No valid legacy installation found for migration');
                return;
            }

            $this->log_debug('Starting legacy migration process');

            // Step 1: Migrate settings
            $this->migrate_legacy_settings($legacy_settings);

            // Step 2: Create named connections from legacy adapters  
            $this->migrate_legacy_adapters($legacy_settings);

            // Note: We intentionally do NOT migrate legacy cache log entries
            // Pro should start with a clean cache state for better performance
            $this->log_debug('Cache log migration skipped - Pro will start with clean cache state');

            $this->log_debug('Legacy migration completed successfully');

        } catch (\Throwable $e) {
            $this->log_debug('Legacy migration error: ' . $e->getMessage());
            // Don't fail installation if migration fails - log and continue
        }
    }

    /**
     * Get legacy settings as associative array
     * 
     * @return array
     */
    private function get_legacy_settings(): array
    {
        // Check if legacy settings table exists
        if (!ee()->db->table_exists('jcogs_img_settings')) {
            return [];
        }

        $legacy_settings = ee()->db->get('jcogs_img_settings')->result_array();
        $settings_by_name = [];
        
        foreach ($legacy_settings as $setting) {
            $settings_by_name[$setting['setting_name']] = $setting['value'];
        }

        return $settings_by_name;
    }

    /**
     * Check if legacy JCOGS Image addon exists and has valid license
     * 
     * @param array $legacy_settings
     * @return bool
     */
    private function is_legacy_addon_valid(array $legacy_settings): bool
    {
        // Check if legacy addon is installed
        $legacy_addon = ee('Addon')->get('jcogs_img');
        if (!$legacy_addon || !$legacy_addon->isInstalled()) {
            return false;
        }

        // Check if legacy settings table exists
        if (empty($legacy_settings)) {
            return false;
        }

        // Check for valid license - only 'magic' or 'valid' are acceptable
        $license_key = $legacy_settings['jcogs_license_mode'] ?? '';
        
        return in_array($license_key, ['magic', 'valid']);
    }

    /**
     * Migrate legacy settings to Pro settings table
     * 
     * @param array $legacy_settings
     * @return void
     */
    private function migrate_legacy_settings(array $legacy_settings): void
    {
        if (empty($legacy_settings)) {
            return;
        }

        $settings_service = ee('jcogs_img_pro:Settings');
        $pro_settings = $settings_service::$pro_settings;

        // Define settings to exclude (obsolete in Pro)
        $excluded_settings = [
            'jcogs_license_key',
            'jcogs_license_key_email', 
            'jcogs_staging_domain',
            'jcogs_license_mode',
            'jcogs_license_server_domain',
            'img_cp_default_cache_directory',
            'img_cp_flysystem_adapter',
            'jcogs_add_on_version'  // Preserve Pro version, don't overwrite with legacy
        ];

        // Migrate all legacy settings except excluded ones
        $migrated_count = 0;
        foreach ($legacy_settings as $legacy_name => $legacy_value) {
            // Skip excluded settings
            if (in_array($legacy_name, $excluded_settings)) {
                continue;
            }

            // Skip settings beginning with excluded prefixes
            if (strpos($legacy_name, 'img_cp_flysystem_adapter_') === 0 ||
                strpos($legacy_name, 'img_cp_ee5') === 0) {
                continue;
            }

            // Migrate all other settings directly
            $pro_settings[$legacy_name] = $legacy_value;
            $migrated_count++;
            $this->log_debug("Migrated setting {$legacy_name}");
        }

        // Save updated Pro settings
        $settings_service::$pro_settings = $pro_settings;
        $settings_service->save_settings();

        $this->log_debug("Settings migration completed ({$migrated_count} settings migrated)");
    }

    /**
     * Create named connections from legacy adapter configurations
     * 
     * @param array $legacy_settings
     * @return void
     */
    private function migrate_legacy_adapters(array $legacy_settings): void
    {
        $connections = [];

        // Always add local connection with updated cache directory
        $legacy_cache_dir = $legacy_settings['img_cp_default_cache_directory'] ?? 'images/jcogs_img_pro/cache';
        // Update legacy cache path to use Pro directory structure
        $pro_cache_dir = $this->update_cache_path_for_pro($legacy_cache_dir);
        
        $connections['legacy_local'] = [
            'type' => 'local',
            'is_valid' => true,
            'config' => [
                'cache_directory' => $pro_cache_dir,
                'always_output_full_urls' => $legacy_settings['img_cp_class_always_output_full_urls'] ?? 'n',
                'path_prefix' => $legacy_settings['img_cp_cdn_path_prefix'] ?? ''
            ]
        ];

        // Check for S3 configuration
        if (!empty($legacy_settings['img_cp_flysystem_adapter_s3_bucket']) && 
            !empty($legacy_settings['img_cp_flysystem_adapter_s3_key']) &&
            ($legacy_settings['img_cp_flysystem_adapter_s3_is_valid'] ?? 'false') === 'true') {
            $legacy_s3_path = $legacy_settings['img_cp_flysystem_adapter_s3_server_path'] ?? '';
            $connections['legacy_s3'] = [
                'type' => 's3',
                'is_valid' => true,
                'is_legacy' => true,
                'config' => [
                    'key' => $legacy_settings['img_cp_flysystem_adapter_s3_key'] ?? '',
                    'secret' => $legacy_settings['img_cp_flysystem_adapter_s3_secret'] ?? '',
                    'region' => $legacy_settings['img_cp_flysystem_adapter_s3_region'] ?? 'us-east-1',
                    'bucket' => $legacy_settings['img_cp_flysystem_adapter_s3_bucket'] ?? '',
                    'server_path' => $this->update_cache_path_for_pro($legacy_s3_path),
                    'url' => $legacy_settings['img_cp_flysystem_adapter_s3_url'] ?? ''
                ]
            ];
        }

        // Check for R2 configuration
        if (!empty($legacy_settings['img_cp_flysystem_adapter_r2_bucket']) && 
            !empty($legacy_settings['img_cp_flysystem_adapter_r2_key']) &&
            ($legacy_settings['img_cp_flysystem_adapter_r2_is_valid'] ?? 'false') === 'true') {
            $legacy_r2_path = $legacy_settings['img_cp_flysystem_adapter_r2_server_path'] ?? '';
            $connections['legacy_r2'] = [
                'type' => 'r2',
                'is_valid' => true,
                'is_legacy' => true,
                'config' => [
                    'account_id' => $legacy_settings['img_cp_flysystem_adapter_r2_account_id'] ?? '',
                    'key' => $legacy_settings['img_cp_flysystem_adapter_r2_key'] ?? '',
                    'secret' => $legacy_settings['img_cp_flysystem_adapter_r2_secret'] ?? '',
                    'bucket' => $legacy_settings['img_cp_flysystem_adapter_r2_bucket'] ?? '',
                    'server_path' => $this->update_cache_path_for_pro($legacy_r2_path),
                    'url' => $legacy_settings['img_cp_flysystem_adapter_r2_url'] ?? ''
                ]
            ];
        }

        // Check for DigitalOcean Spaces configuration
        if (!empty($legacy_settings['img_cp_flysystem_adapter_dospaces_space']) && 
            !empty($legacy_settings['img_cp_flysystem_adapter_dospaces_key']) &&
            ($legacy_settings['img_cp_flysystem_adapter_dospaces_is_valid'] ?? 'false') === 'true') {
            $legacy_dospaces_path = $legacy_settings['img_cp_flysystem_adapter_dospaces_server_path'] ?? '';
            $connections['legacy_dospaces'] = [
                'type' => 'dospaces',
                'is_valid' => true,
                'is_legacy' => true,
                'config' => [
                    'key' => $legacy_settings['img_cp_flysystem_adapter_dospaces_key'] ?? '',
                    'secret' => $legacy_settings['img_cp_flysystem_adapter_dospaces_secret'] ?? '',
                    'region' => $legacy_settings['img_cp_flysystem_adapter_dospaces_region'] ?? 'nyc3',
                    'space' => $legacy_settings['img_cp_flysystem_adapter_dospaces_space'] ?? '',
                    'server_path' => $this->update_cache_path_for_pro($legacy_dospaces_path),
                    'url' => $legacy_settings['img_cp_flysystem_adapter_dospaces_url'] ?? ''
                ]
            ];
        }

        // Set default connection based on legacy adapter setting
        $default_adapter = $legacy_settings['img_cp_flysystem_adapter'] ?? 'local';
        $default_connection = 'legacy_' . $default_adapter;

        // Ensure the default connection exists in our connections array
        if (!isset($connections[$default_connection])) {
            $default_connection = 'legacy_local'; // Fallback to local
        }

        // Save connections to Pro settings
        if (!empty($connections)) {
            $settings_service = ee('jcogs_img_pro:Settings');
            
            // Create the correct JSON structure with connections and default_connection
            $named_adapters_config = [
                'connections' => $connections,
                'default_connection' => $default_connection
            ];
            
            $settings_service::$pro_settings['img_cp_named_filesystem_adapters'] = json_encode($named_adapters_config);
            $settings_service->save_settings();

            $this->log_debug("Created " . count($connections) . " named connections from legacy adapters (default: {$default_connection})");
        }
    }

    /**
     * Update legacy cache paths to use Pro directory structure
     * 
     * @param string $legacy_path The legacy cache directory path
     * @return string The updated Pro cache directory path
     */
    private function update_cache_path_for_pro(string $legacy_path): string
    {
        // If the path contains 'jcogs_img' but not 'jcogs_img_pro', update it
        if (str_contains($legacy_path, 'jcogs_img') && !str_contains($legacy_path, 'jcogs_img_pro')) {
            // Replace 'jcogs_img' with 'jcogs_img_pro'
            $updated_path = str_replace('jcogs_img', 'jcogs_img_pro', $legacy_path);
            $this->log_debug("Updated cache path from '{$legacy_path}' to '{$updated_path}'");
            return $updated_path;
        }
        
        // If path doesn't contain jcogs_img references, ensure it uses Pro structure
        if (!str_contains($legacy_path, 'jcogs_img_pro')) {
            // Default to Pro cache structure
            $default_path = 'images/jcogs_img_pro/cache';
            $this->log_debug("Replaced non-jcogs cache path '{$legacy_path}' with Pro default '{$default_path}'");
            return $default_path;
        }
        
        // Path already uses Pro structure
        return $legacy_path;
    }

    /**
     * Initialize settings with defaults
     * 
     * @return void
     */
    private function initialize_settings(): void
    {
        try {
            // Initialize settings service and save defaults
            ee('jcogs_img_pro:Settings')->save_settings();
            $this->log_debug('Settings initialized with defaults');
        } catch (\Throwable $e) {
            $this->log_debug('Error initializing settings: ' . $e->getMessage());
        }
    }

    /**
     * Clear various EE caches for safety
     * 
     * @return void
     */
    private function clear_caches(): void
    {
        // Clear OPcache if available
        if (function_exists('opcache_reset')) {
            $opcache_cleared = opcache_reset();
            
            if ($opcache_cleared) {
                $this->log_debug('OPcache reset successfully.');
            } else {
                $this->log_debug('OPcache reset attempted but failed (possibly restricted).');
            }
        }

        // Clear EE cache
        ee()->cache->delete('/jcogs_img_pro/');
    }

    /**
     * Removes Windows-only HEIC converter artifacts on non-Windows hosts.
     *
     * These files are distributed by upstream dependencies but are only
     * required on Windows runtimes.
     */
    private function prune_windows_heic_binaries_on_non_windows(): void
    {
        $is_windows = false;

        if (defined('PHP_OS_FAMILY')) {
            $is_windows = (PHP_OS_FAMILY === 'Windows');
        } else {
            $is_windows = (stripos(PHP_OS, 'WIN') === 0);
        }

        if ($is_windows) {
            return;
        }

        $files_to_remove = [
            PATH_THIRD . 'jcogs_img_pro/vendor/maestroerror/php-heic-to-jpg/bin/heicToJpg.exe',
            PATH_THIRD . 'jcogs_img_pro/vendor/maestroerror/php-heic-to-jpg/bin/php-heic-to-jpg-windows.zip',
            PATH_THIRD . 'jcogs_img_pro/vendor/bin/heicToJpgWin',
            PATH_THIRD . 'jcogs_img_pro/vendor/bin/heif-converter-windows',
        ];

        foreach ($files_to_remove as $file_path) {
            if (is_file($file_path) && !@unlink($file_path)) {
                $this->log_debug('Failed to remove Windows-only HEIC artifact: ' . $file_path);
            }
        }
    }

    /**
     * Update version metadata during upgrade
     * Ensures database settings reflect current add-on version
     * 
     * @return void
     */
    private function update_version_metadata(): void
    {
        try {
            $settings = ee('jcogs_img_pro:Settings');
            $settings->save_settings([
                'jcogs_add_on_version' => JCOGS_IMG_PRO_VERSION,
                'jcogs_add_on_name' => JCOGS_IMG_PRO_NAME,
                'jcogs_add_on_class' => JCOGS_IMG_PRO_CLASS,
            ]);
            $this->log_debug('Version metadata updated to: ' . JCOGS_IMG_PRO_VERSION);
        } catch (\Throwable $e) {
            $this->log_debug('Error updating version metadata: ' . $e->getMessage());
        }
    }

    /**
     * Update cache performance settings during upgrade
     * 
     * @return void
     */
    private function update_cache_performance_settings(): void
    {
        try {
            // Update current cache count for performance optimization
            $current_count = ee('jcogs_img_pro:ImageUtilities')->get_current_cache_log_count();
            $settings = ee('jcogs_img_pro:Settings');
            $settings::$settings['img_cp_cache_log_current_count'] = $current_count;
            $settings::$settings['img_cp_cache_log_count_last_updated'] = time();
            
            if ($current_count > 10000) {
                // If the current count is greater than 10,000, disable the automatic cache audit
                $settings::$settings['img_cp_enable_cache_audit'] = 'n';
                $this->log_debug('Disabled cache audit due to high cache count (' . number_format($current_count) . ')');
            }
            
            $settings->save_settings();
            $this->log_debug('Cache performance settings updated');
        } catch (\Throwable $e) {
            $this->log_debug('Error updating cache performance settings: ' . $e->getMessage());
        }
    }
}
