<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Cron Setup Controller
 *
 * Provides auto-detected cron job setup instructions based on hosting environment
 */
class Cron_setup extends MY_Controller {

    public function __construct() {
        parent::__construct();
        $this->load_global();

        // Only allow admin/special access
        if (!special_access()) {
            show_error("Access Denied", 403, $heading = "Unauthorized Access!!");
            exit();
        }
    }

    public function index() {
        $data = $this->data;
        $data['page_title'] = 'Auto Backup & Cron Setup';

        // Detect server environment
        $data['server_info'] = $this->detect_server_environment();
        $data['cron_commands'] = $this->generate_cron_commands($data['server_info']);
        $data['backup_status'] = $this->get_backup_status();

        $this->load->view('cron_setup', $data);
    }

    /**
     * Detect server environment
     */
    private function detect_server_environment() {
        $info = array();

        // Get PHP binary path
        $info['php_binary'] = $this->find_php_binary();

        // Get application path
        $info['app_path'] = FCPATH;
        $info['index_file'] = FCPATH . 'index.php';

        // Detect OS
        $info['os'] = PHP_OS;
        $info['is_windows'] = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
        $info['is_linux'] = (strtoupper(substr(PHP_OS, 0, 5)) === 'LINUX');

        // Detect hosting environment
        $info['server_software'] = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'Unknown';
        $info['document_root'] = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '';
        $info['server_name'] = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost';

        // Check for common hosting environments
        $info['is_cpanel'] = $this->is_cpanel();
        $info['is_plesk'] = $this->is_plesk();
        $info['is_directadmin'] = $this->is_directadmin();
        $info['is_localhost'] = $this->is_localhost();
        $info['is_xampp'] = $this->is_xampp();

        // Base URL for web trigger
        $info['base_url'] = base_url();
        $secret_key = $this->get_cron_secret_key();
        $info['cron_url'] = base_url('cron/run?key=' . urlencode($secret_key));
        $info['backup_url'] = base_url('cron/backup?key=' . urlencode($secret_key));
        $info['secret_key'] = $secret_key;

        return $info;
    }

    /**
     * Find PHP binary path
     */
    private function find_php_binary() {
        $is_windows = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
        
        // Common PHP binary paths (check these first, especially for XAMPP)
        $common_paths = array();
        
        if ($is_windows) {
            // Windows paths - check XAMPP first
            $xampp_paths = array(
                'C:/xampp/php/php.exe',
                'D:/xampp/php/php.exe',
                'E:/xampp/php/php.exe',
                str_replace('\\', '/', dirname(FCPATH)) . '/../php/php.exe', // Relative to app
            );
            
            // Try to detect XAMPP path from current path
            if (strpos(FCPATH, 'xampp') !== false || strpos(FCPATH, 'XAMPP') !== false) {
                $parts = explode(DIRECTORY_SEPARATOR, FCPATH);
                foreach ($parts as $i => $part) {
                    if (strtolower($part) === 'htdocs') {
                        $xampp_base = implode(DIRECTORY_SEPARATOR, array_slice($parts, 0, $i));
                        $xampp_paths[] = str_replace('\\', '/', $xampp_base . '/php/php.exe');
                        break;
                    }
                }
            }
            
            $common_paths = array_merge($xampp_paths, array(
                PHP_BINDIR . '/php.exe',
                'C:/wamp64/bin/php/php8.2.0/php.exe',
                'C:/wamp64/bin/php/php8.1.0/php.exe',
                'C:/wamp64/bin/php/php8.0.0/php.exe',
                'C:/wamp64/bin/php/php7.4.0/php.exe',
                'C:/wamp/bin/php/php8.0.0/php.exe',
                'C:/wamp/bin/php/php7.4.0/php.exe',
                'C:/php/php.exe',
            ));
        } else {
            // Linux/Unix paths
            $common_paths = array(
                '/usr/bin/php',
                '/usr/local/bin/php',
                '/usr/bin/php8.2',
                '/usr/bin/php8.1',
                '/usr/bin/php8.0',
                '/usr/bin/php7.4',
                '/opt/cpanel/ea-php82/root/usr/bin/php',
                '/opt/cpanel/ea-php81/root/usr/bin/php',
                '/opt/cpanel/ea-php80/root/usr/bin/php',
                '/opt/cpanel/ea-php74/root/usr/bin/php',
                PHP_BINDIR . '/php',
            );
        }

        // Check common paths first (especially XAMPP on Windows)
        foreach ($common_paths as $path) {
            if (file_exists($path)) {
                // Validate it's actually a PHP executable
                if ($this->is_valid_php_binary($path)) {
                    return $path;
                }
            }
        }

        // Try PHP_BINARY constant, but validate it's actually PHP
        if (defined('PHP_BINARY') && PHP_BINARY) {
            $php_binary = PHP_BINARY;
            // Validate it's actually php.exe/php, not httpd.exe or other executables
            if ($this->is_valid_php_binary($php_binary)) {
                return $php_binary;
            }
        }

        // Fallback
        return $is_windows ? 'php.exe' : '/usr/bin/php';
    }

    /**
     * Validate if a path is actually a PHP executable
     */
    private function is_valid_php_binary($path) {
        if (!file_exists($path)) {
            return false;
        }
        
        // Check filename contains 'php' (case insensitive)
        $filename = basename($path);
        if (stripos($filename, 'php') === false) {
            return false;
        }
        
        // On Windows, exclude httpd.exe and other non-PHP executables
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            $invalid_names = array('httpd.exe', 'apache.exe', 'mysqld.exe', 'mysql.exe');
            foreach ($invalid_names as $invalid) {
                if (strtolower($filename) === $invalid) {
                    return false;
                }
            }
        }
        
        return true;
    }

    /**
     * Check if running on cPanel
     */
    private function is_cpanel() {
        return (
            file_exists('/usr/local/cpanel') ||
            file_exists('/var/cpanel') ||
            strpos($_SERVER['SERVER_SOFTWARE'] ?? '', 'cPanel') !== false ||
            isset($_SERVER['CPANEL'])
        );
    }

    /**
     * Check if running on Plesk
     */
    private function is_plesk() {
        return (
            file_exists('/usr/local/psa') ||
            file_exists('/opt/psa')
        );
    }

    /**
     * Check if running on DirectAdmin
     */
    private function is_directadmin() {
        return file_exists('/usr/local/directadmin');
    }

    /**
     * Check if localhost
     */
    private function is_localhost() {
        $server_name = $_SERVER['SERVER_NAME'] ?? '';
        $server_addr = $_SERVER['SERVER_ADDR'] ?? '';

        return (
            $server_name === 'localhost' ||
            $server_name === '127.0.0.1' ||
            $server_addr === '127.0.0.1' ||
            $server_addr === '::1' ||
            strpos($server_name, '.local') !== false ||
            strpos($server_name, '.test') !== false
        );
    }

    /**
     * Check if XAMPP
     */
    private function is_xampp() {
        return (
            strpos(FCPATH, 'xampp') !== false ||
            strpos(FCPATH, 'XAMPP') !== false ||
            file_exists('C:/xampp') ||
            file_exists('/opt/lampp')
        );
    }

    /**
     * Generate cron commands based on environment
     */
    private function generate_cron_commands($server_info) {
        $commands = array();

        $php_path = $server_info['php_binary'];
        $index_path = $server_info['index_file'];
        $base_url = $server_info['base_url'];

        // CLI Command (Universal)
        $commands['cli'] = array(
            'title' => 'Command Line (CLI)',
            'command' => "{$php_path} {$index_path} cron run",
            'description' => 'Run directly from command line or terminal'
        );

        // Linux Cron (Daily at 2 AM)
        $commands['linux_daily'] = array(
            'title' => 'Linux Cron Job (Daily at 2 AM)',
            'command' => "0 2 * * * {$php_path} {$index_path} cron run >> /dev/null 2>&1",
            'description' => 'Runs backup daily at 2:00 AM'
        );

        // Linux Cron (Every 6 hours)
        $commands['linux_6hours'] = array(
            'title' => 'Linux Cron Job (Every 6 Hours)',
            'command' => "0 */6 * * * {$php_path} {$index_path} cron run >> /dev/null 2>&1",
            'description' => 'Runs backup every 6 hours'
        );

        // cPanel specific
        if ($server_info['is_cpanel']) {
            // Find cPanel PHP version
            $cpanel_php = $this->find_cpanel_php();
            $commands['cpanel'] = array(
                'title' => 'cPanel Cron Job (Recommended)',
                'command' => "0 2 * * * {$cpanel_php} {$index_path} cron run >> /dev/null 2>&1",
                'description' => 'Go to cPanel → Cron Jobs → Add New Cron Job'
            );
        }

        // Windows Task Scheduler
        if ($server_info['is_windows']) {
            $win_php = str_replace('/', '\\', $php_path);
            $win_index = str_replace('/', '\\', $index_path);
            $commands['windows'] = array(
                'title' => 'Windows Task Scheduler',
                'command' => "\"{$win_php}\" \"{$win_index}\" cron run",
                'description' => 'Create a scheduled task in Windows Task Scheduler'
            );

            $commands['windows_bat'] = array(
                'title' => 'Windows Batch File (.bat)',
                'command' => "@echo off\ncd /d \"" . str_replace('/', '\\', dirname($index_path)) . "\"\n\"{$win_php}\" index.php cron run\nexit",
                'description' => 'Save as backup.bat and schedule with Task Scheduler'
            );
        }

        // Web URL Trigger (External Cron Services)
        $commands['web_url'] = array(
            'title' => 'Web URL Trigger (External Cron Services)',
            'command' => $server_info['cron_url'],
            'description' => 'Use with cron-job.org, EasyCron, or similar services'
        );

        // Wget/Curl command
        $commands['wget'] = array(
            'title' => 'Using Wget (Linux)',
            'command' => "0 2 * * * /usr/bin/wget -q -O /dev/null \"{$server_info['cron_url']}\"",
            'description' => 'Alternative using wget to call the URL'
        );

        $commands['curl'] = array(
            'title' => 'Using Curl (Linux)',
            'command' => "0 2 * * * /usr/bin/curl -s \"{$server_info['cron_url']}\" > /dev/null 2>&1",
            'description' => 'Alternative using curl to call the URL'
        );

        return $commands;
    }

    /**
     * Find cPanel PHP binary
     */
    private function find_cpanel_php() {
        $versions = array('82', '81', '80', '74', '73');
        foreach ($versions as $ver) {
            $path = "/opt/cpanel/ea-php{$ver}/root/usr/bin/php";
            if (file_exists($path)) {
                return $path;
            }
        }
        return '/usr/bin/php';
    }

    /**
     * Get backup status
     */
    private function get_backup_status() {
        $status = array();
        $backup_path = FCPATH . 'dbbackup/';

        $status['backup_directory'] = $backup_path;
        $status['directory_exists'] = is_dir($backup_path);
        
        // Create directory if it doesn't exist
        if (!$status['directory_exists']) {
            @mkdir($backup_path, 0755, true);
            $status['directory_exists'] = is_dir($backup_path);
        }
        
        $status['directory_writable'] = $status['directory_exists'] ? is_writable($backup_path) : false;

        if ($status['directory_exists']) {
            // Get all SQL and ZIP files
            $sql_files = glob($backup_path . '*.sql');
            $zip_files = glob($backup_path . '*.zip');
            $files = array_merge($sql_files ? $sql_files : array(), $zip_files ? $zip_files : array());
            $status['total_backups'] = count($files);
            
            // Calculate total size
            $total_size = 0;
            foreach ($files as $file) {
                $total_size += filesize($file);
            }
            $status['total_size'] = $this->format_bytes($total_size);

            if (!empty($files)) {
                // Sort by modification time
                usort($files, function($a, $b) {
                    return filemtime($b) - filemtime($a);
                });

                $status['latest_backup'] = basename($files[0]);
                $status['latest_backup_date'] = date('Y-m-d H:i:s', filemtime($files[0]));
                $status['latest_backup_size'] = $this->format_bytes(filesize($files[0]));
                
                // Get backup age
                $backup_age = time() - filemtime($files[0]);
                $status['latest_backup_age'] = $this->format_time_ago($backup_age);
                
                // Get last 5 backups
                $status['recent_backups'] = array();
                foreach (array_slice($files, 0, 5) as $file) {
                    $status['recent_backups'][] = array(
                        'name' => basename($file),
                        'size' => $this->format_bytes(filesize($file)),
                        'date' => date('Y-m-d H:i:s', filemtime($file))
                    );
                }
            } else {
                $status['latest_backup'] = 'None';
                $status['latest_backup_date'] = 'N/A';
                $status['latest_backup_size'] = '0 B';
                $status['latest_backup_age'] = 'N/A';
                $status['total_size'] = '0 B';
                $status['recent_backups'] = array();
            }

            // Check cron log
            $cron_log = $backup_path . 'cron.log';
            if (file_exists($cron_log)) {
                $status['cron_log_exists'] = true;
                $status['cron_log_size'] = $this->format_bytes(filesize($cron_log));

                // Get last few lines
                $lines = @file($cron_log);
                if ($lines !== false) {
                    $status['last_cron_entries'] = array_slice($lines, -10);
                    $status['last_cron_entry'] = !empty($lines) ? trim(end($lines)) : '';
                } else {
                    $status['last_cron_entries'] = array();
                    $status['last_cron_entry'] = '';
                }
            } else {
                $status['cron_log_exists'] = false;
                $status['last_cron_entries'] = array();
                $status['last_cron_entry'] = '';
            }
        } else {
            $status['total_backups'] = 0;
            $status['total_size'] = '0 B';
            $status['latest_backup'] = 'None';
            $status['latest_backup_date'] = 'N/A';
            $status['latest_backup_size'] = '0 B';
            $status['latest_backup_age'] = 'N/A';
            $status['recent_backups'] = array();
            $status['cron_log_exists'] = false;
            $status['last_cron_entries'] = array();
            $status['last_cron_entry'] = '';
        }

        return $status;
    }

    /**
     * Format time ago
     */
    private function format_time_ago($seconds) {
        if ($seconds < 60) {
            return $seconds . ' seconds ago';
        } elseif ($seconds < 3600) {
            return floor($seconds / 60) . ' minutes ago';
        } elseif ($seconds < 86400) {
            return floor($seconds / 3600) . ' hours ago';
        } else {
            return floor($seconds / 86400) . ' days ago';
        }
    }

    /**
     * Format bytes
     */
    private function format_bytes($bytes, $precision = 2) {
        $units = array('B', 'KB', 'MB', 'GB', 'TB');
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        $bytes /= pow(1024, $pow);
        return round($bytes, $precision) . ' ' . $units[$pow];
    }

    /**
     * Test cron execution
     */
    public function test() {
        $this->load->library('cron');

        echo "<h3>Cron Test</h3>";
        echo "<pre>";
        echo "PHP Binary: " . (defined('PHP_BINARY') ? PHP_BINARY : 'Not defined') . "\n";
        echo "PHP Version: " . PHP_VERSION . "\n";
        echo "OS: " . PHP_OS . "\n";
        echo "Document Root: " . FCPATH . "\n";
        echo "Server Software: " . ($_SERVER['SERVER_SOFTWARE'] ?? 'Unknown') . "\n";
        echo "</pre>";

        echo "<h4>Testing backup creation...</h4>";

        // Try to create a test backup
        $result = $this->run_backup_test();
        echo "<pre>" . $result . "</pre>";
    }

    /**
     * Run backup test
     */
    private function run_backup_test() {
        $this->load->dbutil();

        if (!$this->dbutil->database_exists($this->db->database)) {
            return "ERROR: Database not found";
        }

        return "SUCCESS: Database connection OK. Backup system ready.";
    }

    /**
     * Manual trigger for backup
     */
    public function run_backup() {
        try {
            // Create backup directory if it doesn't exist
            $backup_path = FCPATH . 'dbbackup/';
            if (!is_dir($backup_path)) {
                if (!@mkdir($backup_path, 0755, true)) {
                    throw new Exception('Failed to create backup directory. Please check permissions.');
                }
            }

            // Check if directory is writable
            if (!is_writable($backup_path)) {
                throw new Exception('Backup directory is not writable. Please check permissions.');
            }

            // Load required libraries
            $this->load->dbutil();
            $this->load->helper('file');
            $this->load->database();
            
            // Check database connection
            if (!$this->db->conn_id) {
                throw new Exception('Database connection failed');
            }
            
            // Check if database exists
            if (!$this->dbutil->database_exists($this->db->database)) {
                throw new Exception('Database "' . $this->db->database . '" not found');
            }
            
            // Generate backup filename with timestamp
            $filename = 'backup_' . date('Y-m-d_H-i-s') . '.sql';
            $filepath = $backup_path . $filename;
            
            // Backup preferences
            $prefs = array(
                'tables' => array(),  // Empty array means all tables
                'ignore' => array(),
                'format' => 'txt',    // txt format
                'filename' => $filename,
                'add_drop' => TRUE,
                'add_insert' => TRUE,
                'newline' => "\n"
            );
            
            // Create backup
            $backup = $this->dbutil->backup($prefs);
            
            if (!$backup) {
                throw new Exception('Failed to generate database backup');
            }
            
            // Write the backup file
            if (!write_file($filepath, $backup)) {
                throw new Exception('Failed to write backup file to disk');
            }
            
            // Create compressed version
            $zip_filename = str_replace('.sql', '.zip', $filename);
            $zip_filepath = $backup_path . $zip_filename;
            
            $this->load->library('zip');
            $this->zip->add_data($filename, $backup);
            
            if (!$this->zip->archive($zip_filepath)) {
                throw new Exception('Failed to create compressed backup');
            }
            
            // Remove the uncompressed file after zipping
            @unlink($filepath);
            
            // Log the backup
            $this->log_backup_activity("Manual backup created: $zip_filename");
            
            $this->session->set_flashdata('success', 'Backup created successfully: ' . $zip_filename);
            
        } catch (Exception $e) {
            $error_message = 'Backup failed: ' . $e->getMessage();
            $this->log_backup_activity("ERROR: $error_message");
            $this->session->set_flashdata('error', $error_message);
        }
        
        redirect('cron_setup');
    }

    /**
     * Log backup activity
     */
    private function log_backup_activity($message) {
        $log_path = FCPATH . 'dbbackup/cron.log';
        $timestamp = date('Y-m-d H:i:s');
        $log_entry = "[$timestamp] $message\n";
        
        // Append to log file
        @file_put_contents($log_path, $log_entry, FILE_APPEND | LOCK_EX);
    }

    /**
     * Get cron secret key (configurable)
     */
    private function get_cron_secret_key() {
        // Try to get from config file first
        $config_file = APPPATH . 'config/cron_config.php';
        if (file_exists($config_file)) {
            include($config_file);
            if (isset($cron_secret_key) && !empty($cron_secret_key)) {
                return $cron_secret_key;
            }
        }
        
        // Fallback to default (should be changed in production)
        return 'ncs_cron_secret_2024';
    }

    /**
     * Test cron execution via AJAX
     */
    public function test_cron() {
        header('Content-Type: application/json');
        
        $result = array(
            'success' => false,
            'message' => '',
            'details' => array()
        );
        
        try {
            // Test 1: Check PHP binary
            $php_binary = $this->find_php_binary();
            $result['details']['php_binary'] = $php_binary;
            $result['details']['php_binary_exists'] = file_exists($php_binary);
            $result['details']['php_binary_valid'] = $this->is_valid_php_binary($php_binary);
            
            // Warn if PHP binary seems incorrect
            if ($result['details']['php_binary_exists'] && !$result['details']['php_binary_valid']) {
                $result['message'] = 'Warning: PHP binary path may be incorrect. Expected php.exe, but found: ' . basename($php_binary);
            }
            
            // Test 2: Check index file
            $index_file = FCPATH . 'index.php';
            $result['details']['index_file'] = $index_file;
            $result['details']['index_file_exists'] = file_exists($index_file);
            
            // Test 3: Check backup directory
            $backup_path = FCPATH . 'dbbackup/';
            $result['details']['backup_directory'] = $backup_path;
            $result['details']['backup_dir_exists'] = is_dir($backup_path);
            $result['details']['backup_dir_writable'] = is_writable($backup_path);
            
            // Test 4: Check database connection
            $this->load->database();
            $result['details']['database_connected'] = $this->db->conn_id !== false;
            
            // Test 5: Try to access cron URL
            $secret_key = $this->get_cron_secret_key();
            $cron_url = base_url('cron/run?key=' . urlencode($secret_key));
            $result['details']['cron_url'] = $cron_url;
            
            // All tests passed
            $all_passed = 
                $result['details']['php_binary_exists'] &&
                $result['details']['php_binary_valid'] &&
                $result['details']['index_file_exists'] &&
                ($result['details']['backup_dir_exists'] || is_writable(dirname($backup_path))) &&
                $result['details']['database_connected'];
            
            if ($all_passed) {
                $result['success'] = true;
                if (empty($result['message']) || strpos($result['message'], 'Warning') === false) {
                    $result['message'] = 'All tests passed! Cron setup is ready.';
                }
            } else {
                if (empty($result['message']) || strpos($result['message'], 'Warning') === false) {
                    $result['message'] = 'Some tests failed. Please check the details.';
                }
            }
            
        } catch (Exception $e) {
            $result['message'] = 'Test failed: ' . $e->getMessage();
        }
        
        echo json_encode($result);
    }
}
