<?php

namespace classes;

use RuntimeException;

class coolify_api_client
{
    private string $baseUrl;
    private string $token;
    private int $timeoutSeconds;

    public function __construct(string $baseUrl, string $token, int $timeoutSeconds = 4)
    {
        $this->baseUrl = self::normalizeBaseUrl($baseUrl);
        $this->token = trim($token);
        $this->timeoutSeconds = max(1, $timeoutSeconds);
        if ($this->baseUrl === '' || $this->token === '') {
            throw new RuntimeException('Coolify base URL and API token are required.');
        }
    }

    public static function normalizeBaseUrl(string $baseUrl): string
    {
        $baseUrl = rtrim(trim($baseUrl), '/');
        if ($baseUrl === '') {
            return '';
        }

        if (preg_match('#/api/v[0-9]+$#i', $baseUrl) === 1) {
            return $baseUrl;
        }

        return $baseUrl . '/api/v1';
    }

    public function healthcheck(): array
    {
        return $this->request('GET', '/health', null, false);
    }

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

    public function listServers(): array
    {
        return $this->request('GET', '/servers');
    }

    public function listProjects(): array
    {
        return $this->request('GET', '/projects');
    }

    public function listProjectEnvironments(string $projectUuid): array
    {
        return $this->request('GET', '/projects/' . rawurlencode($projectUuid) . '/environments');
    }

    public function listServices(): array
    {
        return $this->request('GET', '/services');
    }

    public function listApplications(): array
    {
        return $this->request('GET', '/applications');
    }

    public function listGithubApps(): array
    {
        return $this->request('GET', '/github-apps');
    }

    public function getService(string $uuid): array
    {
        return $this->request('GET', '/services/' . rawurlencode($uuid));
    }

    public function createService(array $payload): array
    {
        return $this->request('POST', '/services', $payload);
    }

    public function createPrivateGithubAppApplication(array $payload): array
    {
        return $this->request('POST', '/applications/private-github-app', $payload);
    }

    public function getApplication(string $uuid): array
    {
        return $this->request('GET', '/applications/' . rawurlencode($uuid));
    }

    public function updateApplication(string $uuid, array $payload): array
    {
        return $this->request('PATCH', '/applications/' . rawurlencode($uuid), $payload);
    }

    public function updateService(string $uuid, array $payload): array
    {
        return $this->request('PATCH', '/services/' . rawurlencode($uuid), $payload);
    }

    public function updateServiceEnvsBulk(string $uuid, array $env): array
    {
        if ($env === []) {
            return [];
        }

        return $this->request('PATCH', '/services/' . rawurlencode($uuid) . '/envs/bulk', [
            'data' => self::bulkEnvData($env),
        ]);
    }

    public function updateApplicationEnvsBulk(string $uuid, array $env): array
    {
        if ($env === []) {
            return [];
        }

        return $this->request('PATCH', '/applications/' . rawurlencode($uuid) . '/envs/bulk', [
            'data' => self::bulkEnvData($env),
        ]);
    }

    public function listApplicationEnvs(string $uuid): array
    {
        return $this->request('GET', '/applications/' . rawurlencode($uuid) . '/envs');
    }

    public function deleteApplicationEnv(string $uuid, string $envUuid): array
    {
        return $this->request('DELETE', '/applications/' . rawurlencode($uuid) . '/envs/' . rawurlencode($envUuid));
    }

    private static function bulkEnvData(array $env): array
    {
        $data = [];
        foreach ($env as $key => $value) {
            $data[] = [
                'key' => (string)$key,
                'value' => (string)$value,
                'is_preview' => false,
                'is_literal' => true,
                'is_multiline' => str_contains((string)$value, "\n"),
                'is_shown_once' => false,
            ];
        }

        return $data;
    }

    public function deployResource(string $uuid, bool $force = false): array
    {
        $path = '/deploy?uuid=' . rawurlencode($uuid) . '&force=' . ($force ? 'true' : 'false');
        return $this->request('GET', $path);
    }

    public function startService(string $uuid): array
    {
        return $this->request('GET', '/services/' . rawurlencode($uuid) . '/start');
    }

    public function restartService(string $uuid): array
    {
        return $this->request('GET', '/services/' . rawurlencode($uuid) . '/restart');
    }

    public function restartApplication(string $uuid): array
    {
        return $this->request('GET', '/applications/' . rawurlencode($uuid) . '/restart');
    }

    public function stopService(string $uuid): array
    {
        return $this->request('GET', '/services/' . rawurlencode($uuid) . '/stop');
    }

    public function stopApplication(string $uuid): array
    {
        return $this->request('GET', '/applications/' . rawurlencode($uuid) . '/stop');
    }

    public function deleteService(string $uuid): array
    {
        return $this->request('DELETE', '/services/' . rawurlencode($uuid));
    }

    public function deleteApplication(string $uuid): array
    {
        return $this->request('DELETE', '/applications/' . rawurlencode($uuid));
    }

    public function listDeployments(): array
    {
        return $this->request('GET', '/deployments');
    }

    protected function request(string $method, string $path, ?array $payload = null, bool $versionedApi = true): array
    {
        $url = ($versionedApi ? $this->baseUrl : $this->apiRootUrl()) . '/' . ltrim($path, '/');
        $curl = curl_init($url);
        if ($curl === false) {
            throw new RuntimeException('Could not initialize Coolify API request.');
        }

        $headers = [
            'Accept: application/json',
            'Authorization: Bearer ' . $this->token,
        ];

        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, min(2, $this->timeoutSeconds));
        curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeoutSeconds);
        curl_setopt($curl, CURLOPT_NOSIGNAL, true);
        curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

        if ($payload !== null) {
            $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            if ($body === false) {
                throw new RuntimeException('Could not encode Coolify API payload.');
            }
            $headers[] = 'Content-Type: application/json';
            curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
        }

        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

        $raw = curl_exec($curl);
        $error = curl_error($curl);
        $status = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);

        if ($raw === false) {
            throw new RuntimeException('Coolify API request failed: ' . $error);
        }

        $decoded = null;
        if (trim((string)$raw) !== '') {
            $decoded = json_decode((string)$raw, true);
            if (!is_array($decoded)) {
                $decoded = ['raw' => (string)$raw];
            }
        }

        if ($status < 200 || $status >= 300) {
            $message = is_array($decoded)
                ? (string)($decoded['message'] ?? $decoded['error'] ?? ('HTTP ' . $status))
                : ('HTTP ' . $status);
            if (is_array($decoded)) {
                $details = self::validationErrorSummary($decoded);
                if ($details !== '') {
                    $message .= ': ' . $details;
                }
            }
            throw new RuntimeException('Coolify API request failed: ' . $message);
        }

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

    private static function validationErrorSummary(array $decoded): string
    {
        $errors = $decoded['errors'] ?? $decoded['data']['errors'] ?? null;
        if (!is_array($errors)) {
            return '';
        }

        $parts = [];
        foreach ($errors as $field => $messages) {
            $fieldName = trim((string)$field);
            $fieldPrefix = $fieldName !== '' ? $fieldName . ': ' : '';
            if (is_array($messages)) {
                $messages = implode(', ', array_filter(array_map(static fn(mixed $message): string => trim((string)$message), $messages)));
            } else {
                $messages = trim((string)$messages);
            }
            if ($messages !== '') {
                $parts[] = $fieldPrefix . $messages;
            }
        }

        return implode('; ', array_slice($parts, 0, 5));
    }

    private function apiRootUrl(): string
    {
        return preg_replace('#/v[0-9]+$#i', '', $this->baseUrl) ?: $this->baseUrl;
    }
}
