<?php

namespace PortalPeople\queues;

use PortalPeople\queues\services\QueueMonitorService;
use yii\base\BootstrapInterface;
use yii\base\Module as BaseModule;

class Module extends BaseModule implements BootstrapInterface
{
    public $controllerNamespace = 'PortalPeople\\queues\\controllers';
    public $monitorComponentId = 'ngaugeQueueMonitor';
    public $moduleId = 'ngauge-queues';

    public $supervisorQueues = ['queue', 'mailqueue', 'dfqueue', 'netsuitequeue'];
    public $heartbeatDirectory = '@runtime/queue-heartbeats';
    public $pendingWarning = 25;
    public $pendingCritical = 100;
    public $staleReserveSeconds = 1200;
    public $heartbeatFreshFor = 300;
    public $defaultTableName = '{{%queue}}';
    
    /**
     * @var bool Whether to auto-start recurring jobs on bootstrap
     */
    public $autoStartRecurringJobs = true;
    
    /**
     * @var bool Whether to scan job files for $recurringJob property
     */
    public $scanJobsForRecurringConfig = true;
    
    /**
     * @var array Directories to scan for job files (relative to app root)
     */
    public $jobDirectories = [
        'console/jobs',
        'frontend/jobs',
        'api/jobs',
        'backend/jobs',
        'common/jobs',
        'jobs',
    ];

    public function bootstrap($app)
    {
        $this->id = $this->moduleId;

        if (!isset($app->getModules()[$this->id])) {
            $app->setModule($this->id, $this);
        }

        if ($app->has('urlManager')) {
            $app->urlManager->addRules([
                "{$this->id}" => "{$this->id}/monitor/index",
                "{$this->id}/status" => "{$this->id}/monitor/status",
                "{$this->id}/<action:[\\w\\-]+>" => "{$this->id}/monitor/<action>",
            ], false);
        }

        $this->registerMonitorComponent($app);
        
        // Auto-start recurring jobs for console application
        if ($app instanceof \yii\console\Application && $this->autoStartRecurringJobs) {
            $this->bootstrapRecurringJobs($app);
        }
    }

    protected function registerMonitorComponent($app): void
    {
        if ($app->has($this->monitorComponentId, false)) {
            return;
        }

        $app->set($this->monitorComponentId, [
            'class' => QueueMonitorService::class,
            'supervisorQueues' => $this->supervisorQueues,
            'heartbeatDirectory' => $this->heartbeatDirectory,
            'pendingWarning' => (int) $this->pendingWarning,
            'pendingCritical' => (int) $this->pendingCritical,
            'staleReserveSeconds' => (int) $this->staleReserveSeconds,
            'heartbeatFreshFor' => (int) $this->heartbeatFreshFor,
            'defaultTableName' => $this->defaultTableName,
        ]);
    }
    
    protected function bootstrapRecurringJobs($app)
    {
        // Collect all recurring job configs
        $recurringJobs = [];
        
        // 1. Scan job files for $recurringJob property (auto-discovery)
        if ($this->scanJobsForRecurringConfig) {
            $scannedJobs = $this->scanJobFilesForRecurringJobs($app);
            $recurringJobs = array_merge($recurringJobs, $scannedJobs);
        }
        
        // 2. Load explicit config from params (takes precedence over scanned)
        $explicitJobs = $app->params['settings']['queues']['recurring'] ?? [];
        foreach ($explicitJobs as $config) {
            if (!empty($config['class'])) {
                $recurringJobs[$config['class']] = $config;
            }
        }
        
        if (empty($recurringJobs)) {
            return;
        }
        
        foreach ($recurringJobs as $jobConfig) {
            // Skip disabled jobs
            if (isset($jobConfig['enabled']) && !$jobConfig['enabled']) {
                continue;
            }
            
            // Validate required fields
            if (empty($jobConfig['class'])) {
                \Yii::warning("Recurring job configuration missing 'class' field", __METHOD__);
                continue;
            }
            
            try {
                $this->startRecurringJob($app, $jobConfig);
            } catch (\Exception $e) {
                \Yii::error([
                    'message' => 'Failed to start recurring job: ' . $e->getMessage(),
                    'job' => $jobConfig['class'] ?? 'unknown',
                    'error' => $e->getTraceAsString(),
                ], __METHOD__);
            }
        }
    }
    
    /**
     * Scan job files for $recurringJob property and extract configuration.
     * 
     * @param \yii\base\Application $app
     * @return array Associative array of discovered recurring jobs (key = class name)
     */
    protected function scanJobFilesForRecurringJobs($app)
    {
        $discovered = [];
        
        foreach ($this->jobDirectories as $dir) {
            $jobPath = \Yii::getAlias('@app') . '/' . $dir;
            
            if (!is_dir($jobPath)) {
                continue;
            }
            
            try {
                $files = glob($jobPath . '/*.php');
                
                foreach ($files as $file) {
                    try {
                        $jobConfig = $this->extractRecurringJobConfig($file, $app);
                        if ($jobConfig) {
                            $discovered[$jobConfig['class']] = $jobConfig;
                        }
                    } catch (\Exception $e) {
                        \Yii::warning("Failed to scan job file {$file}: {$e->getMessage()}", __METHOD__);
                    }
                }
            } catch (\Exception $e) {
                \Yii::warning("Failed to scan job directory {$jobPath}: {$e->getMessage()}", __METHOD__);
            }
        }
        
        return $discovered;
    }
    
    /**
     * Extract recurringJob configuration from a job file.
     * 
     * Attempts to:
     * 1. Parse the file to find the class name
     * 2. Instantiate the class
     * 3. Check for a public $recurringJob property
     * 4. Return merged config
     * 
     * @param string $filePath Path to job file
     * @param \yii\base\Application $app
     * @return array|null Configuration array with 'class' key, or null if not a recurring job
     */
    protected function extractRecurringJobConfig($filePath, $app)
    {
        $content = file_get_contents($filePath);
        
        // Extract namespace if present
        $namespace = '';
        if (preg_match('/namespace\s+([\\w\\\\]+);/', $content, $matches)) {
            $namespace = $matches[1];
        }
        
        // Extract class name
        if (!preg_match('/class\s+(\w+)\s+/', $content, $matches)) {
            return null; // No class found
        }
        
        $className = $matches[1];
        $fullClassName = $namespace ? $namespace . '\\' . $className : $className;
        
        // Check if file implements JobInterface
        if (strpos($content, 'JobInterface') === false) {
            return null; // Not a job class
        }
        
        // Try to instantiate and check for recurringJob property
        try {
            if (!class_exists($fullClassName)) {
                return null; // Class doesn't exist (not in autoload)
            }
            
            // Create instance with empty constructor
            $jobInstance = new $fullClassName();
            
            // Check if has recurringJob property
            if (!property_exists($jobInstance, 'recurringJob')) {
                return null; // Not a recurring job
            }
            
            $recurringJobConfig = $jobInstance->recurringJob;
            
            // If recurringJob is just an integer, treat as interval
            if (is_int($recurringJobConfig)) {
                $config = ['interval' => $recurringJobConfig];
            } elseif (is_array($recurringJobConfig)) {
                $config = $recurringJobConfig;
            } else {
                return null; // Invalid recurringJob format
            }
            
            // Set required class and name
            $config['class'] = $fullClassName;
            $config['name'] = $config['name'] ?? "Recurring: {$className}";
            
            // Set defaults for optional fields
            $config['queue'] = $config['queue'] ?? 'queue';
            $config['interval'] = $config['interval'] ?? 3600;
            $config['ttr'] = $config['ttr'] ?? 300;
            $config['enabled'] = $config['enabled'] ?? true;
            $config['checkDuplicates'] = $config['checkDuplicates'] ?? true;
            $config['maxRetries'] = $config['maxRetries'] ?? 3;
            $config['params'] = $config['params'] ?? [];
            
            return $config;
            
        } catch (\Exception $e) {
            // Class couldn't be instantiated - might not be autoloadable yet
            \Yii::debug("Could not instantiate {$fullClassName} to check recurringJob: {$e->getMessage()}", __METHOD__);
            return null;
        }
    }
    
    /**
     * Start a single recurring job if not already queued.
     * 
     * @param \yii\console\Application $app
     * @param array $config Job configuration
     * @return void
     */
    protected function startRecurringJob($app, $config)
    {
        $queueComponent = $config['queue'] ?? 'queue';
        $jobName = $config['name'] ?? "Recurring: {$config['class']}";
        $interval = $config['interval'] ?? 3600;
        $ttr = $config['ttr'] ?? 300;
        $params = $config['params'] ?? [];
        $maxRetries = $config['maxRetries'] ?? 3;
        $checkDuplicates = $config['checkDuplicates'] ?? true;
        
        // Check if job is already queued (prevent duplicates on restart)
        if ($checkDuplicates && $this->isJobAlreadyQueued($app, $queueComponent, $jobName)) {
            \Yii::info("Recurring job '{$jobName}' already queued, skipping", __METHOD__);
            return;
        }
        
        // Get the queue component
        $queue = $app->get($queueComponent);
        
        // Push the wrapper job
        $queue
            ->jobname($jobName)
            ->ttr($ttr)
            ->push(new jobs\RecurringJobWrapper([
                'jobClass' => $config['class'],
                'jobParams' => $params,
                'interval' => $interval,
                'queueComponent' => $queueComponent,
                'jobName' => $jobName,
                'ttr' => $ttr,
                'enabled' => true,
                'maxRetries' => $maxRetries,
            ]));
        
        \Yii::info("Started recurring job: {$jobName}", __METHOD__);
    }
    
    /**
     * Check if a job with the given name is already queued or running.
     * 
     * @param \yii\console\Application $app
     * @param string $queueComponent Queue component name
     * @param string $jobName Job name to check
     * @return bool
     */
    protected function isJobAlreadyQueued($app, $queueComponent, $jobName)
    {
        try {
            $queue = $app->get($queueComponent);
            
            // Get the table name from queue component
            $tableName = $queue->tableName ?? '{{%queue}}';
            
            $count = (new \yii\db\Query())
                ->from($tableName)
                ->where(['jobname' => $jobName])
                ->andWhere(['in', 'status', [1, 2]]) // 1=waiting, 2=running
                ->count();
            
            return $count > 0;
            
        } catch (\Exception $e) {
            \Yii::warning("Could not check for duplicate job: {$e->getMessage()}", __METHOD__);
            return false; // Assume not queued if we can't check
        }
    }
}
