<?php
declare(strict_types=1);

namespace KbMarkdownImporter\GitLab;

final class GitLabClient
{
    private string $baseUrl;
    private string $token;
    private string $branchPattern;

    public function __construct(array $settings)
    {
        $this->baseUrl = self::normalizeBaseUrl((string) ($settings['gitlab_base_url'] ?? ''));
        $this->token = (string) ($settings['gitlab_token'] ?? '');
        $this->branchPattern = (string) ($settings['branch_pattern'] ?? '^v.*');
    }

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

        if (preg_match('#/api/v4$#i', $baseUrl)) {
            $baseUrl = (string) preg_replace('#/api/v4$#i', '', $baseUrl);
        }

        return $baseUrl;
    }

    public function getGroup(string $group): array|\WP_Error
    {
        return $this->request('GET', '/groups/' . rawurlencode($group));
    }

    public function getProjects(string $group): array|\WP_Error
    {
        return $this->requestAll('/groups/' . rawurlencode($group) . '/projects', [
            'include_subgroups' => 'true',
            'simple' => 'true',
            'order_by' => 'path',
            'sort' => 'asc',
        ]);
    }

    public function getProject(string $projectId): array|\WP_Error
    {
        return $this->request('GET', '/projects/' . rawurlencode($projectId));
    }

    public function getBranches(string $projectId): array|\WP_Error
    {
        return $this->requestAll('/projects/' . rawurlencode($projectId) . '/repository/branches', []);
    }

    public function getDocumentationBranches(string $projectId): array|\WP_Error
    {
        $branches = $this->getBranches($projectId);

        if (is_wp_error($branches)) {
            return $branches;
        }

        $pattern = '/' . str_replace('/', '\/', $this->branchPattern) . '/';

        return array_values(array_filter($branches, static function (array $branch) use ($pattern): bool {
            return isset($branch['name']) && @preg_match($pattern, (string) $branch['name']);
        }));
    }

    public function getFileRaw(string $projectId, string $path, string $ref): string|\WP_Error
    {
        $response = $this->rawRequest('/projects/' . rawurlencode($projectId) . '/repository/files/' . rawurlencode($path) . '/raw', [
            'ref' => $ref,
        ]);

        if (is_wp_error($response)) {
            return $response;
        }

        return wp_remote_retrieve_body($response);
    }

    public function getTree(string $projectId, string $ref, string $path = '', bool $recursive = true): array|\WP_Error
    {
        return $this->requestAll('/projects/' . rawurlencode($projectId) . '/repository/tree', [
            'ref' => $ref,
            'path' => $path,
            'recursive' => $recursive ? 'true' : 'false',
        ]);
    }

    private function requestAll(string $endpoint, array $query): array|\WP_Error
    {
        $page = 1;
        $items = [];

        do {
            $response = $this->rawRequest($endpoint, array_merge($query, [
                'per_page' => '100',
                'page' => (string) $page,
            ]));

            if (is_wp_error($response)) {
                return $response;
            }

            $decoded = json_decode(wp_remote_retrieve_body($response), true);

            if (! is_array($decoded)) {
                return new \WP_Error('kb_gitlab_invalid_json', __('GitLab returned invalid JSON.', 'kb-markdown-importer'));
            }

            $items = array_merge($items, $decoded);
            $next = wp_remote_retrieve_header($response, 'x-next-page');
            $page = $next ? (int) $next : 0;
        } while ($page > 0);

        return $items;
    }

    private function request(string $method, string $endpoint, array $query = []): array|\WP_Error
    {
        $response = $this->rawRequest($endpoint, $query, $method);

        if (is_wp_error($response)) {
            return $response;
        }

        $decoded = json_decode(wp_remote_retrieve_body($response), true);

        if (! is_array($decoded)) {
            return new \WP_Error('kb_gitlab_invalid_json', __('GitLab returned invalid JSON.', 'kb-markdown-importer'));
        }

        return $decoded;
    }

    private function rawRequest(string $endpoint, array $query = [], string $method = 'GET'): array|\WP_Error
    {
        if (! $this->baseUrl || ! $this->token) {
            return new \WP_Error('kb_gitlab_missing_settings', __('GitLab base URL or token is missing.', 'kb-markdown-importer'));
        }

        $url = $this->baseUrl . '/api/v4' . $endpoint;

        if ($query) {
            $url = add_query_arg($query, $url);
        }

        $response = wp_remote_request($url, [
            'method' => $method,
            'timeout' => 30,
            'headers' => [
                'PRIVATE-TOKEN' => $this->token,
                'Accept' => 'application/json',
            ],
        ]);

        if (is_wp_error($response)) {
            return $response;
        }

        $code = (int) wp_remote_retrieve_response_code($response);

        if ($code >= 200 && $code < 300) {
            return $response;
        }

        $body = wp_strip_all_tags(wp_remote_retrieve_body($response));
        $body = trim(preg_replace('/\s+/', ' ', $body) ?? $body);
        $body = substr($body, 0, 300);
        $retryAfter = wp_remote_retrieve_header($response, 'retry-after');
        $message = sprintf(
            /* translators: %d is an HTTP status code. */
            __('GitLab API request failed with HTTP %d.', 'kb-markdown-importer'),
            $code
        );

        if (503 === $code) {
            $message .= ' ' . __('The GitLab server or a proxy returned Service Unavailable. Check whether GitLab is reachable from the WordPress server and whether the Base URL points to the GitLab root, not to /api/v4.', 'kb-markdown-importer');
        }

        return new \WP_Error(
            'kb_gitlab_http_' . $code,
            $message,
            [
                'status' => $code,
                'url' => esc_url_raw($url),
                'retry_after' => $retryAfter ? (string) $retryAfter : '',
                'response_excerpt' => $body,
            ]
        );
    }
}
