<?php

namespace App\Livewire\Docker;

use App\Livewire\Concerns\WithFleetContext;
use App\Models\AuditEvent;
use App\Models\HostCredential;
use App\Models\Server;
use App\Services\DockerService;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth;
use InvalidArgumentException;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Throwable;

/**
 * Container management over SSH. Two targets: the CLUSEV HOST itself (the machine Clusev runs on,
 * reached via the stored HostCredential over the local Docker gateway) and any fleet server. The
 * host is the default when configured — that is where the operator's own containers live. Viewing
 * the list is open to any role; container actions (start/stop/restart) require `operate`, matching
 * systemd service gating. Loads lazily via wire:init like Files/Services.
 */
#[Layout('layouts.app')]
class Index extends Component
{
    use WithFleetContext;

    /** '' = auto (host when a host login exists, else the active fleet server); or 'host' / 'server'. */
    public string $target = '';

    /** @var array<int, array<string, string>> */
    public array $containers = [];

    public bool $connected = false;

    public bool $ready = false;

    /** Docker binary absent on the target (e.g. a native mail server) — an honest state, not an error. */
    public bool $notInstalled = false;

    /** Host target selected but no host SSH login configured yet — points the operator to setup. */
    public bool $hostNotConfigured = false;

    /** The docker error (daemon down / permission / rootless), surfaced so "empty" isn't misleading. */
    public ?string $error = null;

    public function title(): string
    {
        return __('docker.title');
    }

    /** Whether a Clusev-host SSH login exists (controls the default target + the setup hint). */
    public function hostConfigured(): bool
    {
        return HostCredential::current() !== null;
    }

    /**
     * Only admins (manage-fleet) may target the Clusev host — the same bar as the host terminal.
     * The host is the control-plane machine: a `Stop` on clusev-mariadb would take Clusev down, so
     * a viewer/operator must never reach it (they only ever see fleet servers).
     */
    public function mayUseHost(): bool
    {
        return (bool) Auth::user()?->can('manage-fleet');
    }

    /** Show the host/server switch only to users allowed on the host. */
    public function canToggleHost(): bool
    {
        return $this->mayUseHost();
    }

    /** Resolve the target: the host (admins only) or a fleet server; '' auto-picks the host default. */
    public function effectiveTarget(): string
    {
        // An explicit host selection is honoured only for permitted users; everyone else is pinned
        // to a fleet server so a viewer/operator can never reach the control-plane machine.
        if ($this->target === 'host' && $this->mayUseHost()) {
            return 'host';
        }
        if ($this->target === 'server') {
            return 'server';
        }

        return ($this->mayUseHost() && $this->hostConfigured()) ? 'host' : 'server';
    }

    /** The Server the current target resolves to (transient host server, or the active fleet server). */
    private function targetServer(): ?Server
    {
        return $this->effectiveTarget() === 'host'
            ? HostCredential::current()?->toServer()
            : $this->activeServer();
    }

    /** Switch between the host and the active fleet server, then reload. */
    public function setTarget(string $target, DockerService $docker): void
    {
        $this->target = in_array($target, ['host', 'server'], true) ? $target : '';
        $this->ready = false;
        $this->load($docker);
    }

    public function load(DockerService $docker): void
    {
        $this->containers = [];
        $this->connected = false;
        $this->notInstalled = false;
        $this->hostNotConfigured = false;
        $this->error = null;

        $isHost = $this->effectiveTarget() === 'host';
        $server = $this->targetServer();

        if ($isHost && ! $server) {
            $this->hostNotConfigured = true;
            $this->ready = true;

            return;
        }

        // Fleet servers need a stored credential; the host server always carries the host login.
        if ($server && ($isHost || $server->credential_exists)) {
            try {
                // Probe first: a target with no docker binary (native mail/web server) gets a clean
                // "not installed" panel instead of the raw "sh: docker: not found" shell error.
                if (! $docker->available($server)) {
                    $this->notInstalled = true;
                } else {
                    $this->containers = $docker->containers($server);
                    $this->connected = true;
                }
            } catch (Throwable $e) {
                $this->connected = false;
                $this->error = mb_scrub(mb_strcut($e->getMessage(), 0, 300), 'UTF-8');
            }
        }

        $this->ready = true;
    }

    public function action(string $id, string $op, DockerService $docker): void
    {
        abort_unless(Auth::user()?->can('operate'), 403);

        $isHost = $this->effectiveTarget() === 'host';
        if ($isHost) {
            // Host container actions match the host-terminal bar: admin-only (stopping a
            // control-plane container is as destructive as a root shell on the host).
            abort_unless(Auth::user()?->can('manage-fleet'), 403);
        }
        $server = $this->targetServer();
        if (! $server || (! $isHost && ! $server->credential_exists)) {
            return;
        }

        try {
            $res = $docker->containerAction($server, $id, $op);
            AuditEvent::create([
                'user_id' => Auth::id(),
                'server_id' => $server->id, // null for the host (transient) — the target label names it
                'actor' => Auth::user()?->name ?? 'system',
                'action' => 'docker.action',
                'target' => "{$op} {$id} · {$server->name}",
                'ip' => request()->ip(),
            ]);
            $this->dispatch('notify', message: $res['ok']
                ? __('docker.action_ok', ['op' => $op, 'ref' => $id])
                : __('docker.action_failed', ['error' => $res['output']]));
        } catch (InvalidArgumentException) {
            $this->dispatch('notify', message: __('docker.invalid_ref'));
        }

        $this->load($docker);
    }

    /** Open the read-only logs modal for a container (the ref is re-validated in DockerService). */
    public function viewLogs(string $id): void
    {
        // Container logs can contain secrets (env dumps, tokens in traces), so reading them is an
        // operate action — consistent with gating file CONTENT reads. The list itself stays open.
        abort_unless(Auth::user()?->can('operate'), 403);

        $isHost = $this->effectiveTarget() === 'host';
        if ($isHost) {
            abort_unless(Auth::user()?->can('manage-fleet'), 403); // host logs: admin-only, like the host shell
        }
        $server = $this->targetServer();
        if (! $server) {
            return;
        }

        $this->dispatch('openModal', component: 'modals.container-logs', arguments: [
            // 0 is the sentinel the logs modal resolves to the Clusev host (no persisted id).
            'serverId' => $isHost ? 0 : (int) $server->id,
            'ref' => $id,
        ]);
    }

    public function render(): View
    {
        return view('livewire.docker.index')->title($this->title());
    }
}
