<?php

namespace App\Commands;

use App\Models\ConfigurationModel;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use DateTime;
use DateTimeZone;

class ConfigUpdate extends BaseCommand
{
    protected $group       = 'Maintenance';
    protected $name        = 'config:update';
    protected $description = 'Run a configuration update task (weekly cron).';
    protected $arguments   = [];
    protected $usage = 'php spark config:update [task] [--task task|-t task] [--dry] [--force] [--tz=<timezone>]';
    protected $options = [
        'task'  => 'Task name (or pass as first positional arg)',
        't'     => 'Short form of --task',
        'dry'   => 'Dry run (no DB writes)',
        'force' => 'Ignore lock and run anyway',
        'tz'    => 'Timezone (default: configured school timezone)',
    ];


    /** @var ConfigurationModel */
    protected $configModel;

    /** Map of available task names -> handler methods. */
    protected array $tasks = [
        'update_date_age_reference' => 'taskUpdateDateAgeReference',
        'enable_attendance_on'  => 'taskEnableAttendanceOn',
        'enable_attendance_off' => 'taskEnableAttendanceOff',
        'set_semester_spring' => 'taskSetSemesterSpring',
        'set_semester_fall'   => 'taskSetSemesterFall',
    ];


    /**
     * Set enable_attendance = 1
     */
    protected function taskEnableAttendanceOn(bool $dry, DateTimeZone $tz): bool
    {
        return $this->setConfig('enable_attendance', '1', $dry);
    }

    /**
     * Set enable_attendance = 0
     */
    protected function taskEnableAttendanceOff(bool $dry, DateTimeZone $tz): bool
    {
        return $this->setConfig('enable_attendance', '0', $dry);
    }

    protected function taskUpdateDateAgeReference(bool $dry, DateTimeZone $tz): bool
    {
        $now   = new DateTime('now', $tz);
        $isJune1 = ($now->format('n') === '6' && $now->format('j') === '1');
        $forced  = (CLI::getOption('force') !== null);

        if (!$isJune1 && !$forced) {
            CLI::write(
                "Today is {$now->format('Y-m-d')} (not June 1) — skipping. Use --force to override.",
                'yellow'
            );
            return true; // no-op, not an error
        }

        $year  = (int) $now->format('Y');
        $value = sprintf('%04d-12-31', $year);

        CLI::write("Set date_age_reference = {$value}" . ($dry ? ' [DRY]' : ''), 'light_gray');
        if ($dry) return true;

        // Use your model method
        $ok = (bool) $this->configModel->setConfigValueByKey('date_age_reference', $value);

        if ($ok) {
            CLI::write("date_age_reference updated to {$value}", 'green');
        } else {
            CLI::error("Failed to update date_age_reference");
        }
        return $ok;
    }

    protected function taskSetSemesterSpring(bool $dry, DateTimeZone $tz): bool
    {
        $now    = new DateTime('now', $tz);
        $isFeb1 = ($now->format('n') === '2' && $now->format('j') === '1');
        $forced = (CLI::getOption('force') !== null);

        if (!$isFeb1 && !$forced) {
            CLI::write(
                "Today is {$now->format('Y-m-d')} (not Feb 1) — skipping. Use --force to override.",
                'yellow'
            );
            return true; // no-op is success
        }

        CLI::write("Set semester = Spring" . ($dry ? ' [DRY]' : ''), 'light_gray');
        if ($dry) return true;

        return (bool) $this->configModel->setConfigValueByKey('semester', 'Spring');
    }

    protected function taskSetSemesterFall(bool $dry, DateTimeZone $tz): bool
    {
        $now     = new DateTime('now', $tz);
        $isJun1  = ($now->format('n') === '6' && $now->format('j') === '1');
        $forced  = (CLI::getOption('force') !== null);

        if (!$isJun1 && !$forced) {
            CLI::write(
                "Today is {$now->format('Y-m-d')} (not Jun 1) — skipping. Use --force to override.",
                'yellow'
            );
            return true;
        }

        CLI::write("Set semester = Fall" . ($dry ? ' [DRY]' : ''), 'light_gray');
        if ($dry) return true;

        return (bool) $this->configModel->setConfigValueByKey('semester', 'Fall');
    }

    public function run(array $params)
    {
        $this->configModel = model(ConfigurationModel::class);

        $tz    = $this->getOptionString('tz', $params) ?? ((string)(config('School')->attendance['timezone'] ?? 'UTC'));
        $task  = $this->getOptionString('task', $params, 't');

        // Fallback: first positional arg (php spark config:update enable_attendance_on)
        if (!$task && !empty($params) && strpos($params[0], '-') !== 0) {
            $task = trim($params[0]);
        }

        $dry   = $this->hasFlag('dry', $params);
        $force = $this->hasFlag('force', $params);
        $dtz   = new DateTimeZone($tz);

        if ($task === '' || !isset($this->tasks[$task])) {
            CLI::error('Invalid or missing --task. Available: ' . implode(', ', array_keys($this->tasks)));
            return;
        }

        // Per-task lock
        $lockFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "ci4_config_update_{$task}.lock";
        $fp = @fopen($lockFile, 'c+');
        if (!$fp) {
            CLI::error("Unable to open lock file: $lockFile");
            return;
        }
        if (!$force && !flock($fp, LOCK_EX | LOCK_NB)) {
            CLI::error("Task '$task' is already running (lock held). Use --force to override.");
            fclose($fp);
            return;
        }

        try {
            $method = $this->tasks[$task];
            CLI::write("Running task: {$task}" . ($dry ? ' [DRY RUN]' : ''), 'yellow');
            $ok = $this->{$method}($dry, $dtz);
            if ($ok === true) {
                CLI::write("Task '{$task}' finished successfully.", 'green');
            } else {
                CLI::error("Task '{$task}' finished with warnings or no changes.");
            }
        } catch (\Throwable $e) {
            CLI::error("Task '{$task}' failed: " . $e->getMessage());
        } finally {
            try {
                flock($fp, LOCK_UN);
                fclose($fp);
                @unlink($lockFile);
            } catch (\Throwable $e) {
            }
        }
    }

    /** Accepts --name=value, --name value, -s value, or scans $params. */
    private function getOptionString(string $name, array $params, ?string $short = null): ?string
    {
        $v = CLI::getOption($name);
        if (is_string($v) && $v !== '') return trim($v);
        if ($short) {
            $v = CLI::getOption($short);
            if (is_string($v) && $v !== '') return trim($v);
        }
        foreach ($params as $i => $p) {
            if (strpos($p, "--{$name}=") === 0) return trim(substr($p, strlen($name) + 3));
            if ($short && strpos($p, "-{$short}=") === 0) return trim(substr($p, strlen($short) + 2));
            if ($p === "--{$name}" || ($short && $p === "-{$short}")) {
                return $params[$i + 1] ?? null;
            }
        }
        return null;
    }

    /** True if flag present as --name or -s (no value needed). */
    private function hasFlag(string $name, array $params, ?string $short = null): bool
    {
        if (CLI::getOption($name) !== null) return true;
        if ($short && CLI::getOption($short) !== null) return true;
        foreach ($params as $p) {
            if ($p === "--{$name}" || ($short && $p === "-{$short}")) return true;
        }
        return false;
    }


    /* ------------------------- Helpers ------------------------- */
    protected function setConfig(string $key, string $value, bool $dry): bool
    {
        // show current value
        $current = $this->configModel->where('config_key', $key)->first()['config_value'] ?? '<NULL>';
        CLI::write("{$key}: current={$current}", 'light_gray');

        CLI::write("Set {$key} = {$value}" . ($dry ? ' [DRY]' : ''), 'light_gray');
        if ($dry) return true;

        // ✅ use your model function
        $ok = (bool) $this->configModel->setConfigValueByKey($key, $value);

        // read-back to verify what’s persisted
        $after = $this->configModel->where('config_key', $key)->first()['config_value'] ?? '<NULL>';
        CLI::write("{$key}: after={$after}", $ok ? 'green' : 'red');

        return $ok && ($after === $value);
    }
}
