<?php

namespace App\Jobs;

use App\Models\AiAction;
use App\Services\AuditLogger;
use App\Services\Exec\ServerExecutor;
use App\Services\Snapshot\SnapshotService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

/**
 * Executes an approved AiAction. Status flow:
 *   approved → running → completed (exit 0) | failed (non-zero / error)
 *
 * Captures stdout/stderr/exit code on the action row. Writes one audit
 * entry per execution. Skips if the action's status changed underneath us
 * (e.g., kill switch flipped status to rejected mid-flight).
 *
 * Re-audit hook: after a successful execution we also flip the parent
 * Issue to status=fixed (the auditor's "post-fix verification" step). If
 * the next scheduled audit re-detects the same finding, the issue reopens.
 */
class ExecuteAiAction implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 1;             // Don't retry shell commands automatically.
    public int $timeout = 90;

    public function __construct(public int $actionId) {}

    public function handle(ServerExecutor $executor, SnapshotService $snapshotter): void
    {
        /** @var AiAction|null $action */
        $action = AiAction::with('server', 'issue')->find($this->actionId);
        if (! $action) return;

        // Idempotency / kill-switch guards.
        if ($action->status !== AiAction::STATUS_APPROVED) {
            // Already running, completed, or rejected — nothing to do.
            return;
        }

        // Honor system-wide AI freeze.
        $state = \DB::table('system_state')->first();
        if ($state && $state->ai_frozen) {
            $action->update([
                'status' => AiAction::STATUS_FAILED,
                'output' => 'Aborted: AI operations frozen by kill switch',
                'completed_at' => now(),
            ]);
            return;
        }

        $action->update([
            'status'      => AiAction::STATUS_RUNNING,
            'executed_at' => now(),
        ]);

        // Snapshot before destructive ops. If snapshot fails AND it's
        // critical-risk, abort — better to refuse than to fix without
        // a rollback path.
        if ($snapshotter->shouldSnapshot($action->server, $action->risk)) {
            $snap = $snapshotter->take($action->server, "action_{$action->id}");
            if ($snap['ok']) {
                $action->update(['snapshot_id' => $snap['snapshot_id']]);
            } elseif ($action->risk === AiAction::RISK_CRITICAL) {
                $action->update([
                    'status'       => AiAction::STATUS_FAILED,
                    'output'       => "Aborted: snapshot required for critical risk but failed.\n" . ($snap['error'] ?? '?'),
                    'completed_at' => now(),
                ]);
                return;
            }
        }

        $result = $executor->run($action->server, $action->command, (int) config('hsap.execute.timeout', 60));

        $success = $result['exit_code'] === 0 && ! $result['error'];

        $action->update([
            'status'       => $success ? AiAction::STATUS_COMPLETED : AiAction::STATUS_FAILED,
            'completed_at' => now(),
            'exit_code'    => $result['exit_code'],
            'output'       => $result['output'] . ($result['error'] ? "\n[error] " . $result['error'] : ''),
            'ai_metadata'  => array_merge($action->ai_metadata ?? [], [
                'exec_mode'    => $result['mode'],
                'duration_ms'  => $result['duration_ms'],
            ]),
        ]);

        // Mark the parent issue as fixed when the command succeeds. Future
        // scans will re-open it if the underlying problem recurs.
        if ($success && $action->issue) {
            $action->issue->update([
                'status' => 'fixed',
                'resolved_at' => now(),
                'resolved_by_action' => $action->id,
            ]);
        }

        AuditLogger::log('system', 'executor', 'ai-executor',
            'ai.action_' . ($success ? 'completed' : 'failed'),
            'ai_action', (string) $action->id,
            [
                'server'       => $action->server->hostname,
                'mode'         => $result['mode'],
                'exit_code'    => $result['exit_code'],
                'duration_ms'  => $result['duration_ms'],
                'error'        => $result['error'],
            ]);
    }
}
