<?php

namespace App\Modules\Academic\Services;

/**
 * Dapodik WebService API client.
 * Uses env: DAPODIK_BASE_URL, DAPODIK_TOKEN, DAPODIK_NPSN.
 * Do not log or expose token.
 */
class DapodikClient
{
    protected string $baseUrl;
    protected string $token;
    protected string $npsn;

    public function __construct(?string $baseUrl = null, ?string $token = null, ?string $npsn = null)
    {
        $this->baseUrl = rtrim($baseUrl ?? (string) env('DAPODIK_BASE_URL', ''), '/');
        $this->token  = $token ?? (string) env('DAPODIK_TOKEN', '');
        $this->npsn   = $npsn ?? (string) env('DAPODIK_NPSN', '');
    }

    /**
     * GET getSekolah
     *
     * @return array{success: bool, data?: array, error?: string}
     */
    public function getSekolah(): array
    {
        $url = $this->baseUrl . '/getSekolah';
        if ($this->npsn !== '') {
            $url .= '?npsn=' . rawurlencode($this->npsn);
        }
        return $this->request('GET', $url);
    }

    /**
     * GET getPesertaDidik with pagination
     *
     * @param int $start
     * @param int $limit
     * @return array{success: bool, data?: array, rows?: array, id?: mixed, start?: int, limit?: int, results?: int, error?: string}
     */
    public function getPesertaDidik(int $start = 0, int $limit = 200): array
    {
        $params = [];
        if ($this->npsn !== '') {
            $params['npsn'] = $this->npsn;
        }
        $params['start'] = $start;
        $params['limit'] = $limit;
        $url = $this->baseUrl . '/getPesertaDidik?' . http_build_query($params);
        return $this->request('GET', $url);
    }

    /**
     * Execute HTTP request. Returns decoded JSON with success flag and error message on failure.
     */
    protected function request(string $method, string $url): array
    {
        // Dapodik WebService commonly expects `token` as query param.
        // We still send Authorization header for compatibility.
        if ($this->token !== '' && strpos($url, 'token=') === false) {
            $url = $this->appendQueryParam($url, 'token', $this->token);
        }

        $ch = curl_init();
        if ($ch === false) {
            return ['success' => false, 'error' => 'cURL init failed'];
        }

        $headers = ['Accept: application/json'];
        if ($this->token !== '') {
            $headers[] = 'Authorization: Bearer ' . $this->token;
        }

        curl_setopt_array($ch, [
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 60,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_CUSTOMREQUEST  => $method,
        ]);

        $body = curl_exec($ch);
        $errno = curl_errno($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $contentType = (string) (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?? '');
        curl_close($ch);

        if ($errno) {
            return ['success' => false, 'error' => 'Network error: ' . ($errno === CURLE_OPERATION_TIMEDOUT ? 'timeout' : 'curl ' . $errno)];
        }

        $bodyStr = is_string($body) ? $body : '';
        if (trim($bodyStr) === '') {
            return ['success' => false, 'error' => 'Empty response from Dapodik', 'http_code' => $httpCode, 'content_type' => $contentType];
        }

        $decoded = json_decode($bodyStr, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            $snippet = substr(trim(strip_tags($bodyStr)), 0, 200);
            $extra = $snippet !== '' ? ' — ' . $snippet : '';
            $codePart = $httpCode > 0 ? ' (HTTP ' . $httpCode . ')' : '';
            return ['success' => false, 'error' => 'Invalid JSON response' . $codePart . $extra, 'http_code' => $httpCode, 'content_type' => $contentType];
        }

        if ($httpCode < 200 || $httpCode >= 300) {
            $msg = is_array($decoded) && isset($decoded['message']) ? $decoded['message'] : 'HTTP ' . $httpCode;
            return ['success' => false, 'error' => $msg, 'http_code' => $httpCode, 'data' => $decoded];
        }

        return array_merge(['success' => true], $decoded);
    }

    /**
     * Append a query param to URL, preserving fragment.
     */
    private function appendQueryParam(string $url, string $key, string $value): string
    {
        $frag = '';
        $base = $url;
        $hashPos = strpos($url, '#');
        if ($hashPos !== false) {
            $base = substr($url, 0, $hashPos);
            $frag = substr($url, $hashPos);
        }

        $sep = (strpos($base, '?') === false) ? '?' : '&';
        return $base . $sep . rawurlencode($key) . '=' . rawurlencode($value) . $frag;
    }

    /**
     * Normalize Dapodik response to a list of rows.
     * Dapodik returns { results, id, start, limit, rows: [...] }
     *
     * @param array $response Response from getPesertaDidik
     * @return array<int, array<string, mixed>>
     */
    public static function normalizePesertaDidikRows(array $response): array
    {
        if (! isset($response['rows']) || ! is_array($response['rows'])) {
            return [];
        }
        $rows = $response['rows'];
        $out = [];
        foreach ($rows as $i => $row) {
            $out[] = is_array($row) ? $row : (array) $row;
        }
        return $out;
    }
}
