<?php

/**
 * CSS Cache Cleanup Background Task
 *
 * Cleans up old and unused CSS cache files
 *
 * @package ProRank\SEO\Core\Tasks
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\Tasks;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}


/**
 * CssCacheCleanupTask class
 */


class CssCacheCleanupTask {
    
    /**
     * Task hook name
     *
     * @var string
     */
    const HOOK_NAME = 'prorank_css_cache_cleanup';

    /**
     * Group used for recurring cleanup scheduling.
     *
     * @var string
     */
    const RECURRING_GROUP = 'prorank-seo';
    
    /**
     * Cleanup interval (daily)
     *
     * @var int
     */
    const CLEANUP_INTERVAL = DAY_IN_SECONDS;
    
    /**
     * Maximum cache age in days
     *
     * @var int
     */
    const MAX_CACHE_AGE_DAYS = 30;
    
    /**
     * Constructor
     */
    public function __construct() {
    }
    
    /**
     * Initialize the task
     *
     * @return void
     */
    public function init(): void {
        // Register the action for Action Scheduler
        add_action(self::HOOK_NAME, [$this, 'run_cleanup'], 10, 0);
        
        // Schedule recurring cleanup if not scheduled
        if (!$this->has_scheduled_cleanup()) {
            $this->schedule_cleanup();
        }
        
        // Hook into cache clear actions
        add_action('prorank_css_cache_cleared', [$this, 'on_cache_cleared']);
        add_action('switch_theme', [$this, 'on_theme_switch']);
        add_action('upgrader_process_complete', [$this, 'on_plugin_update'], 10, 2);
    }
    
    /**
     * Run cleanup process
     *
     * @return void
     */
    public function run_cleanup(): void {
        try {
            $start_time = microtime(true);
            
            // Get settings
            $settings = get_option('prorank_css_optimize_settings', []);
            $max_age_days = $settings['css_cache_lifetime'] ?? self::MAX_CACHE_AGE_DAYS;
            
            // Clean up old cache files
            $files_removed = $this->clean_old_files($max_age_days);
            
            // Clean up orphaned database entries
            $entries_removed = $this->clean_orphaned_entries();
            
            // Clean up empty directories
            $dirs_removed = $this->clean_empty_directories();
            
            // Optimize database tables
            $this->optimize_tables();
            
            // Calculate execution time
            $execution_time = microtime(true) - $start_time;
            
            // Log cleanup results
            $this->log_cleanup_results([
                'files_removed' => $files_removed,
                'entries_removed' => $entries_removed,
                'dirs_removed' => $dirs_removed,
                'execution_time' => $execution_time,
                'timestamp' => time(),
            ]);
            
        } catch (\Exception $e) {
            // Log error
            prorank_log('ProRank CSS Cache Cleanup Error: ' . $e->getMessage());
            
            // Retry in 1 hour
            as_schedule_single_action(
                time() + HOUR_IN_SECONDS,
                self::HOOK_NAME,
                [],
                'prorank-seo'
            );
        }
    }
    
    /**
     * Clean old cache files
     *
     * @param int $max_age_days Maximum age in days
     * @return int Number of files removed
     */
    private function clean_old_files(int $max_age_days): int {
        $cache_dir = wp_upload_dir()['basedir'] . '/prorank-cache/css';
        
        if (!is_dir($cache_dir)) {
            return 0;
        }
        
        $files_removed = 0;
        $max_age_seconds = $max_age_days * DAY_IN_SECONDS;
        $current_time = time();
        
        // Scan cache directory
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($cache_dir, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::LEAVES_ONLY
        );
        
        foreach ($iterator as $file) {
            if (!$file->isFile()) {
                continue;
            }
            
            // Check file age
            $file_age = $current_time - $file->getMTime();
            
            if ($file_age > $max_age_seconds) {
                // Check if file is still in use
                if (!$this->is_file_in_use($file->getPathname())) {
                    \ProRank\SEO\Core\FilesystemHelper::delete_file($file->getPathname());
                    $files_removed++;
                }
            }
        }
        
        return $files_removed;
    }
    
    /**
     * Clean orphaned database entries
     *
     * @return int Number of entries removed
     */
    private function clean_orphaned_entries(): int {
        global $wpdb;
        
        $entries_removed = 0;
        
        // Clean CSS cache table
        $css_table = $wpdb->prefix . 'prorank_css_cache';
        if ($this->table_exists($css_table)) {
            // Remove entries where file doesn't exist
            $results = $wpdb->get_results(
                "SELECT id, cache_key FROM {$css_table} WHERE status = 'processed'",
                ARRAY_A
            );
            
            $cache_dir = wp_upload_dir()['basedir'] . '/prorank-cache/css';
            
            foreach ($results as $row) {
                $cache_file = $cache_dir . '/' . $row['cache_key'] . '.css';
                
                if (!file_exists($cache_file)) {
                    $wpdb->delete($css_table, ['id' => $row['id']], ['%d']);
                    $entries_removed++;
                }
            }
        }
        
        // Clean external CSS queue table
        $external_table = $wpdb->prefix . 'prorank_external_css_queue';
        if ($this->table_exists($external_table)) {
            // Remove old failed entries
            $wpdb->query($wpdb->prepare(
                "DELETE FROM {$external_table} 
                 WHERE status = 'failed' 
                 AND processed_at < DATE_SUB(NOW(), INTERVAL %d DAY)",
                7 // Remove failed entries older than 7 days
            ));
            
            $entries_removed += $wpdb->rows_affected;
        }
        
        return $entries_removed;
    }
    
    /**
     * Clean empty directories
     *
     * @return int Number of directories removed
     */
    private function clean_empty_directories(): int {
        $cache_dir = wp_upload_dir()['basedir'] . '/prorank-cache';
        
        if (!is_dir($cache_dir)) {
            return 0;
        }
        
        $dirs_removed = 0;
        
        // Function to recursively remove empty directories
        $remove_empty_dirs = function($dir) use (&$remove_empty_dirs, &$dirs_removed) {
            if (!is_dir($dir)) {
                return;
            }
            
            $files = array_diff(scandir($dir), ['.', '..']);
            
            foreach ($files as $file) {
                $path = $dir . '/' . $file;
                
                if (is_dir($path)) {
                    $remove_empty_dirs($path);
                }
            }
            
            // Check if directory is empty after processing subdirectories
            $files = array_diff(scandir($dir), ['.', '..']);
            
            if (empty($files) && $dir !== wp_upload_dir()['basedir'] . '/prorank-cache') {
                \ProRank\SEO\Core\FilesystemHelper::delete_dir($dir);
                $dirs_removed++;
            }
        };
        
        $remove_empty_dirs($cache_dir);
        
        return $dirs_removed;
    }
    
    /**
     * Optimize database tables
     *
     * @return void
     */
    private function optimize_tables(): void {
        global $wpdb;
        
        $tables = [
            $wpdb->prefix . 'prorank_css_cache',
            $wpdb->prefix . 'prorank_external_css_queue',
        ];
        
        foreach ($tables as $table) {
            if ($this->table_exists($table)) {
                $wpdb->query("OPTIMIZE TABLE {$table}");
            }
        }
    }
    
    /**
     * Check if file is still in use
     *
     * @param string $file_path File path
     * @return bool
     */
    private function is_file_in_use(string $file_path): bool {
        // Check if this is a currently used cache file
        $cache_key = basename($file_path, '.css');
        
        global $wpdb;
        $table = $wpdb->prefix . 'prorank_css_cache';
        
        if ($this->table_exists($table)) {
            $count = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$table} 
                 WHERE cache_key = %s 
                 AND status = 'processed'",
                $cache_key
            ));
            
            return $count > 0;
        }
        
        return false;
    }
    
    /**
     * Check if table exists
     *
     * @param string $table_name Table name
     * @return bool
     */
    private function table_exists(string $table_name): bool {
        global $wpdb;
        
        return $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        ) === $table_name;
    }
    
    /**
     * Log cleanup results
     *
     * @param array $results Cleanup results
     * @return void
     */
    private function log_cleanup_results(array $results): void {
        // Store last cleanup results
        update_option('prorank_css_cache_last_cleanup', $results);
        
        // Update cleanup history
        $history = get_option('prorank_css_cache_cleanup_history', []);
        
        // Keep last 30 cleanup records
        array_unshift($history, $results);
        $history = array_slice($history, 0, 30);
        
        update_option('prorank_css_cache_cleanup_history', $history);
        
        // Log to file if debug is enabled
        if (defined('WP_DEBUG') && WP_DEBUG) {
            prorank_log(sprintf(
                'ProRank CSS Cache Cleanup: Removed %d files, %d DB entries, %d directories in %.2f seconds',
                $results['files_removed'],
                $results['entries_removed'],
                $results['dirs_removed'],
                $results['execution_time']
            ));
        }
    }
    
    /**
     * Schedule cleanup task
     *
     * @return void
     */
    private function schedule_cleanup(): void {
        if ($this->has_scheduled_cleanup()) {
            return;
        }

        as_schedule_recurring_action(
            strtotime('tomorrow 3:00 am'), // Run at 3 AM daily
            self::CLEANUP_INTERVAL,
            self::HOOK_NAME,
            [],
            self::RECURRING_GROUP
        );
    }

    /**
     * Check whether a recurring cleanup is already scheduled.
     *
     * @return bool
     */
    private function has_scheduled_cleanup(): bool {
        if (function_exists('as_has_scheduled_action')) {
            return (bool) as_has_scheduled_action(self::HOOK_NAME, [], self::RECURRING_GROUP);
        }

        if (function_exists('as_next_scheduled_action')) {
            return (bool) as_next_scheduled_action(self::HOOK_NAME, [], self::RECURRING_GROUP);
        }

        return false;
    }
    
    /**
     * Handle cache cleared event
     *
     * @return void
     */
    public function on_cache_cleared(): void {
        // Run immediate cleanup
        as_enqueue_async_action(self::HOOK_NAME, [], 'prorank-seo');
    }
    
    /**
     * Handle theme switch event
     *
     * @return void
     */
    public function on_theme_switch(): void {
        // Clear all CSS cache on theme switch
        $this->clear_all_cache();
        
        // Run cleanup
        as_enqueue_async_action(self::HOOK_NAME, [], 'prorank-seo');
    }
    
    /**
     * Handle plugin update event
     *
     * @param object $upgrader Upgrader instance
     * @param array $hook_extra Extra hook data
     * @return void
     */
    public function on_plugin_update($upgrader, $hook_extra): void {
        if ($hook_extra['type'] !== 'plugin') {
            return;
        }
        
        // Check if our plugin was updated
        $our_plugin = plugin_basename(PRORANK_PLUGIN_FILE);
        
        if (isset($hook_extra['plugins']) && in_array($our_plugin, $hook_extra['plugins'])) {
            // Clear cache after our plugin update
            $this->clear_all_cache();
            
            // Run cleanup
            as_enqueue_async_action(self::HOOK_NAME, [], 'prorank-seo');
        }
    }
    
    /**
     * Clear all CSS cache
     *
     * @return void
     */
    private function clear_all_cache(): void {
        $upload_dir = wp_upload_dir();
        $cache_base = rtrim((string) ($upload_dir['basedir'] ?? ''), '/');

        if ($cache_base !== '') {
            // Keep CSS/HTML/JS cache invalidation in sync so page cache never serves
            // stale markup with outdated CSS delivery mode after updates/settings changes.
            $directories = [
                $cache_base . '/prorank-cache/css',
                $cache_base . '/prorank-cache/html',
                $cache_base . '/prorank-cache/js',
            ];

            foreach ($directories as $directory) {
                $this->clear_cache_directory($directory);
            }
        }
        
        // Clear database entries
        global $wpdb;
        
        $tables = [
            $wpdb->prefix . 'prorank_css_cache',
            $wpdb->prefix . 'prorank_external_css_queue',
        ];
        
        foreach ($tables as $table) {
            if ($this->table_exists($table)) {
                $wpdb->query($wpdb->prepare("TRUNCATE TABLE %i", $table));
            }
        }

        // Let other performance modules (edge cache integrations, observers, etc.)
        // react consistently to this purge path.
        do_action('prorank_cache_cleared', 'css_cache_cleanup', null);
    }

    /**
     * Remove all files/directories inside a cache directory.
     *
     * @param string $directory
     * @return void
     */
    private function clear_cache_directory(string $directory): void {
        if ($directory === '' || !is_dir($directory)) {
            return;
        }

        try {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS),
                \RecursiveIteratorIterator::CHILD_FIRST
            );
        } catch (\Throwable $e) {
            return;
        }

        foreach ($iterator as $file) {
            if ($file->isDir()) {
                \ProRank\SEO\Core\FilesystemHelper::delete_dir($file->getPathname());
            } else {
                \ProRank\SEO\Core\FilesystemHelper::delete_file($file->getPathname());
            }
        }
    }
}
