<?php

declare(strict_types=1);

namespace Origin\Engine;

use RuntimeException;
use InvalidArgumentException;
use JsonException;

class EngineClient
{
    private string $engineHost;
    private int $enginePort;
    private string $hmacKey;
    private float $timeout;
    private int $maxRetries;

    public function __construct(
        string $engineHost = '10.69.69.6',
        int $enginePort = 8301,
        ?string $hmacKeyPath = null,
        float $timeout = 10.0,
        int $maxRetries = 3
    ) {
        $this->engineHost = $engineHost;
        $this->enginePort = $enginePort;
        $this->timeout = $timeout;
        $this->maxRetries = $maxRetries;

        $keyPath = $hmacKeyPath ?? '/var/www/private/bridge_hmac.key';
        if (!file_exists($keyPath)) {
            throw new RuntimeException('HMAC key file not found: ' . $keyPath);
        }
        $this->hmacKey = trim(file_get_contents($keyPath));
        if (strlen($this->hmacKey) < 32) {
            throw new RuntimeException('HMAC key must be at least 32 bytes');
        }
    }

    public function submitArtifact(string $signalType, array $payload): array
    {
        $body = json_encode([
            'signal_type' => $signalType,
            'payload' => $payload,
            'submitted_at' => date('c'),
        ], JSON_THROW_ON_ERROR);

        return $this->request('POST', '/artifact', ['body' => $body, 'hmac' => true]);
    }

    public function checkPayment(string $paymentId): array
    {
        if (!preg_match('/^[a-zA-Z0-9_-]{1,128}$/', $paymentId)) {
            throw new InvalidArgumentException('Invalid payment ID format');
        }

        return $this->request('GET', '/payment/status/' . $paymentId, ['hmac' => false]);
    }

    public function initPayment(array $order): array
    {
        $required = ['amount', 'currency', 'method'];
        foreach ($required as $field) {
            if (empty($order[$field])) {
                throw new InvalidArgumentException("Missing required field: {$field}");
            }
        }

        $body = json_encode($order, JSON_THROW_ON_ERROR);

        return $this->request('POST', '/payment/init', ['body' => $body, 'hmac' => true]);
    }

    public function sendEmail(string $recipient, string $template, array $vars): array
    {
        if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email address');
        }

        $body = json_encode([
            'recipient' => $recipient,
            'template' => $template,
            'variables' => $vars,
            'sent_at' => date('c'),
        ], JSON_THROW_ON_ERROR);

        return $this->request('POST', '/email/send', ['body' => $body, 'hmac' => true]);
    }

    public function startFulfillment(string $orderRef, array $shipping): array
    {
        if (!preg_match('/^[A-Z0-9-]{4,64}$/i', $orderRef)) {
            throw new InvalidArgumentException('Invalid order reference format');
        }

        $body = json_encode([
            'order_ref' => $orderRef,
            'shipping' => $shipping,
            'started_at' => date('c'),
        ], JSON_THROW_ON_ERROR);

        return $this->request('POST', '/fulfillment/start', ['body' => $body, 'hmac' => true]);
    }

    public function reportSecurityEvent(array $event): bool
    {
        $required = ['severity', 'type', 'source'];
        foreach ($required as $field) {
            if (empty($event[$field])) {
                throw new InvalidArgumentException("Missing required field: {$field}");
            }
        }

        $severity = strtolower($event['severity']);
        if (!in_array($severity, ['low', 'medium', 'high', 'critical'], true)) {
            throw new InvalidArgumentException('Invalid severity level');
        }

        $body = json_encode([
            'event' => $event,
            'reported_at' => date('c'),
            'reporter' => 'NODE_669427',
        ], JSON_THROW_ON_ERROR);

        $this->request('POST', '/artifact', [
            'body' => json_encode([
                'signal_type' => 'security_event',
                'payload' => $event,
            ], JSON_THROW_ON_ERROR),
            'hmac' => true,
        ]);

        return true;
    }

    public function healthCheck(): bool
    {
        try {
            $response = $this->request('GET', '/health', ['hmac' => false, 'timeout' => 3.0]);
            return ($response['status'] ?? '') === 'ok';
        } catch (RuntimeException) {
            return false;
        }
    }

    public function getEngineStatus(): array
    {
        return $this->request('GET', '/status', ['hmac' => false]);
    }

    private function hmacSign(string $method, string $path, string $payload): array
    {
        $ts = (string)time();
        $nonce = bin2hex(random_bytes(16));
        // CRY-02 canonical: HMAC-SHA256( ts.nonce.METHOD.path.body )
        $data = $ts . '.' . $nonce . '.' . $method . '.' . $path . '.' . $payload;
        $sig = hash_hmac('sha256', $data, $this->hmacKey);

        return [
            'ts' => $ts,
            'nonce' => $nonce,
            'sig' => $sig,
        ];
    }

    private function request(string $method, string $endpoint, array $options = []): array
    {
        $useHmac = $options['hmac'] ?? false;
        $body = $options['body'] ?? null;
        $timeout = $options['timeout'] ?? $this->timeout;
        $lastError = null;

        for ($attempt = 0; $attempt < $this->maxRetries; $attempt++) {
            try {
                return $this->doRequest($method, $endpoint, $body, $useHmac, $timeout);
            } catch (RuntimeException $e) {
                $lastError = $e;
                if ($attempt < $this->maxRetries - 1) {
                    usleep(($attempt + 1) * 100000);
                }
            }
        }

        throw new RuntimeException(
            'ENGINE request failed after ' . $this->maxRetries . ' attempts: ' . $lastError?->getMessage()
        );
    }

    private function doRequest(
        string $method,
        string $endpoint,
        ?string $body,
        bool $useHmac,
        float $timeout
    ): array {
        $url = "http://{$this->engineHost}:{$this->enginePort}{$endpoint}";

        $headers = [
            'Content-Type: application/json',
            'Accept: application/json',
            'User-Agent: Origin-Client/1.0 (NODE_669427)',
        ];

        if ($useHmac && $body !== null) {
            $sig = $this->hmacSign($method, $endpoint, $body);
            $headers[] = 'x-engine-ts: ' . $sig['ts'];
            $headers[] = 'x-engine-nonce: ' . $sig['nonce'];
            $headers[] = 'x-engine-sig: ' . $sig['sig'];
        }

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_TIMEOUT_MS => (int)($timeout * 1000),
            CURLOPT_CONNECTTIMEOUT_MS => 2000,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_HEADER => false,
        ]);

        if ($body !== null && $method !== 'GET') {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }

        $response = curl_exec($ch);
        $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErr = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException('CURL error: ' . $curlErr);
        }

        if ($httpCode === 401) {
            throw new RuntimeException('ENGINE HMAC authentication failed');
        }

        if ($httpCode === 404) {
            throw new RuntimeException('ENGINE endpoint not found: ' . $endpoint);
        }

        if ($httpCode >= 500) {
            throw new RuntimeException('ENGINE server error: HTTP ' . $httpCode);
        }

        if ($httpCode >= 400) {
            throw new RuntimeException('ENGINE client error: HTTP ' . $httpCode);
        }

        try {
            $decoded = json_decode((string)$response, true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            throw new RuntimeException('Invalid JSON response from ENGINE');
        }

        if (isset($decoded['error'])) {
            throw new RuntimeException('ENGINE error: ' . $decoded['error']);
        }

        return is_array($decoded) ? $decoded : ['response' => $decoded];
    }
}
