<?php

namespace App\Modules\Vm\Proxmox;

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;

/**
 * PRC-56 — Proxmox VE REST API wrapper.
 *
 * Auth modes:
 *   1. API token (recommended) → header: PVEAPIToken=USER@REALM!TOKENID=SECRET
 *   2. Password ticket — POST /access/ticket → cookie PVEAuthCookie + CSRFPreventionToken header
 *
 * Returns shape: ['ok' => bool, 'data' => mixed] | ['ok' => false, 'error' => string, 'status' => int]
 */
class ProxmoxClient
{
    private const TIMEOUT = 30;

    protected string $apiUrl;

    protected string $authMode;   // 'api_token' | 'password'

    protected ?string $username;

    protected ?string $password;

    protected ?string $tokenId;       // user@realm!tokenname

    protected ?string $tokenSecret;

    protected bool $verifyTls;

    protected ?string $ticket = null;       // cached when password mode

    protected ?string $csrfToken = null;

    public function __construct(array $config)
    {
        $this->apiUrl = rtrim((string) ($config['api_url'] ?? ''), '/');
        $this->authMode = (string) ($config['auth_method'] ?? 'api_token');
        $this->username = $config['username'] ?? null;
        $this->password = $config['password'] ?? null;
        $this->tokenId = $config['api_token_id'] ?? null;
        $this->tokenSecret = $config['api_token_secret'] ?? null;
        $this->verifyTls = (bool) ($config['verify_tls'] ?? true);
    }

    public function hasCredentials(): bool
    {
        if ($this->apiUrl === '') {
            return false;
        }
        if ($this->authMode === 'api_token') {
            return ! empty($this->tokenId) && ! empty($this->tokenSecret);
        }

        return ! empty($this->username) && ! empty($this->password);
    }

    public function getVersion(): array
    {
        return $this->call('GET', '/version');
    }

    public function listNodes(): array
    {
        return $this->call('GET', '/nodes');
    }

    /**
     * Clone a template VM into a new VM. Caller chooses target vmid.
     *
     * Body: { newid, name, target?, full?, storage?, ... }
     */
    public function cloneVm(string $node, int $sourceVmId, array $payload): array
    {
        return $this->call('POST', "/nodes/{$node}/qemu/{$sourceVmId}/clone", $payload);
    }

    public function setVmConfig(string $node, int $vmid, array $config): array
    {
        return $this->call('PUT', "/nodes/{$node}/qemu/{$vmid}/config", $config);
    }

    public function startVm(string $node, int $vmid): array
    {
        return $this->call('POST', "/nodes/{$node}/qemu/{$vmid}/status/start");
    }

    public function stopVm(string $node, int $vmid): array
    {
        return $this->call('POST', "/nodes/{$node}/qemu/{$vmid}/status/stop");
    }

    public function rebootVm(string $node, int $vmid): array
    {
        return $this->call('POST', "/nodes/{$node}/qemu/{$vmid}/status/reboot");
    }

    public function deleteVm(string $node, int $vmid): array
    {
        return $this->call('DELETE', "/nodes/{$node}/qemu/{$vmid}");
    }

    public function getVmStatus(string $node, int $vmid): array
    {
        return $this->call('GET', "/nodes/{$node}/qemu/{$vmid}/status/current");
    }

    public function vncProxy(string $node, int $vmid): array
    {
        return $this->call('POST', "/nodes/{$node}/qemu/{$vmid}/vncproxy");
    }

    /**
     * Find next free VM id in the cluster.
     */
    public function nextVmId(): array
    {
        return $this->call('GET', '/cluster/nextid');
    }

    protected function call(string $method, string $path, array $body = []): array
    {
        if (! $this->hasCredentials()) {
            return ['ok' => false, 'error' => 'Proxmox credentials missing', 'status' => 0];
        }
        try {
            $req = $this->buildRequest($method, $path);
            $url = $this->apiUrl.$path;

            $resp = match ($method) {
                'GET' => $req->get($url, $body),
                'POST' => $req->asForm()->post($url, $body),
                'PUT' => $req->asForm()->put($url, $body),
                'DELETE' => $req->delete($url),
            };

            if (! $resp->successful()) {
                $err = $resp->json('errors') ?? $resp->json('message') ?? "HTTP {$resp->status()}";

                return [
                    'ok' => false,
                    'error' => is_string($err) ? $err : json_encode($err),
                    'status' => $resp->status(),
                ];
            }

            // PVE responses wrap payload under "data"
            $json = $resp->json();
            $data = is_array($json) && array_key_exists('data', $json) ? $json['data'] : $json;

            return ['ok' => true, 'data' => $data];
        } catch (\Throwable $e) {
            return ['ok' => false, 'error' => $e->getMessage(), 'status' => 0];
        }
    }

    /**
     * Build authenticated PendingRequest. Password mode obtains a ticket
     * lazily (single roundtrip per client instance).
     */
    protected function buildRequest(string $method, string $path): PendingRequest
    {
        $req = Http::withOptions(['verify' => $this->verifyTls])
            ->acceptJson()
            ->timeout(self::TIMEOUT);

        if ($this->authMode === 'api_token') {
            return $req->withHeaders([
                'Authorization' => "PVEAPIToken={$this->tokenId}={$this->tokenSecret}",
            ]);
        }

        // Password mode — fetch ticket on demand
        if ($this->ticket === null) {
            $this->fetchTicket();
        }

        $headers = ['Cookie' => "PVEAuthCookie={$this->ticket}"];
        if ($method !== 'GET' && $this->csrfToken) {
            $headers['CSRFPreventionToken'] = $this->csrfToken;
        }

        return $req->withHeaders($headers);
    }

    protected function fetchTicket(): void
    {
        $resp = Http::withOptions(['verify' => $this->verifyTls])
            ->acceptJson()
            ->timeout(self::TIMEOUT)
            ->asForm()
            ->post($this->apiUrl.'/access/ticket', [
                'username' => $this->username,
                'password' => $this->password,
            ]);
        if (! $resp->successful()) {
            throw new \RuntimeException('Proxmox ticket auth failed: HTTP '.$resp->status());
        }
        $data = $resp->json('data') ?? [];
        $this->ticket = $data['ticket'] ?? null;
        $this->csrfToken = $data['CSRFPreventionToken'] ?? null;
        if (! $this->ticket) {
            throw new \RuntimeException('Proxmox ticket auth: missing ticket in response');
        }
    }
}
