<?php

namespace classes;

use Exception;

class edge_broker_transport_exception extends Exception
{
    public function __construct(string $message, private readonly int $curlErrno = 0, int $code = 0, ?Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }

    public function curlErrno(): int
    {
        return $this->curlErrno;
    }
}

class edge_broker_http_exception extends Exception
{
    public function __construct(string $message, private readonly int $statusCode, int $code = 0, ?Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }

    public function statusCode(): int
    {
        return $this->statusCode;
    }
}

class edge_broker_client
{
    private const DEFAULT_BROKER_URL = 'http://edge-broker:4300';

    public function __construct(
        private readonly ?string $baseUrl = null,
        private readonly ?string $sharedSecret = null,
        private readonly int $timeoutSeconds = 10
    ) {
    }

    public function isConfigured(): bool
    {
        return trim((string)$this->resolveBaseUrl()) !== '';
    }

    public function dispatchCommand(int $gatewayId, string $commandType, array $payload): array
    {
        $url = rtrim($this->resolveBaseUrl(), '/') . '/api/gateways/' . $gatewayId . '/commands';
        $response = $this->request('POST', $url, [
            'commandType' => $commandType,
            'payload' => $payload,
        ]);

        return is_array($response) ? $response : ['ok' => false, 'response' => $response];
    }

    public function validateAgent(int $gatewayId, string $agentToken): array
    {
        $url = rtrim($this->resolveBaseUrl(), '/') . '/api/internal/agent/auth';
        $response = $this->request('POST', $url, [
            'gatewayId' => $gatewayId,
            'agentToken' => $agentToken,
        ]);

        return is_array($response) ? $response : [];
    }

    public function validateShellSession(string $sessionToken): array
    {
        $url = rtrim($this->resolveBaseUrl(), '/') . '/api/internal/shell/auth';
        $response = $this->request('POST', $url, [
            'sessionToken' => $sessionToken,
        ]);

        return is_array($response) ? $response : [];
    }

    public function closeShellSession(int $sessionId, string $sessionToken, string $transcript, string $closedReason): array
    {
        $url = rtrim($this->resolveBaseUrl(), '/') . '/api/internal/shell-sessions/' . $sessionId . '/close';
        $response = $this->request('POST', $url, [
            'sessionToken' => $sessionToken,
            'transcript' => $transcript,
            'closedReason' => $closedReason,
        ]);

        return is_array($response) ? $response : [];
    }

    private function resolveBaseUrl(): string
    {
        return trim((string)($this->baseUrl ?? getenv('EDGE_BROKER_URL') ?: self::DEFAULT_BROKER_URL));
    }

    private function resolveSharedSecret(): string
    {
        return trim((string)($this->sharedSecret
            ?? getenv('EDGE_BROKER_SHARED_SECRET')
            ?: getenv('EDGE_INTERNAL_SECRET')
            ?: ''));
    }

    /**
     * @throws Exception
     */
    private function request(string $method, string $url, array $payload): array|object|null
    {
        if (trim($url) === '') {
            throw new Exception('Edge broker URL is not configured');
        }

        $sharedSecret = $this->resolveSharedSecret();
        if ($sharedSecret === '') {
            throw new Exception('Edge broker shared secret is not configured');
        }

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeoutSeconds);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'X-Edge-Broker-Secret: ' . $sharedSecret,
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_UNICODE));

        $rawResponse = curl_exec($ch);
        $statusCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErrno = curl_errno($ch);
        $curlError = curl_error($ch);
        curl_close($ch);

        if ($rawResponse === false) {
            throw new edge_broker_transport_exception('Edge broker request failed: ' . $curlError, $curlErrno);
        }

        $decoded = json_decode((string)$rawResponse, true);
        if ($statusCode >= 400) {
            $message = is_array($decoded)
                ? (string)($decoded['error'] ?? $decoded['message'] ?? 'Edge broker request failed')
                : 'Edge broker request failed';
            throw new edge_broker_http_exception($message, $statusCode);
        }

        return $decoded;
    }
}
