<?php

declare(strict_types=1);

namespace App\Services;

use CodeIgniter\HTTP\CURLRequest;
use Config\Services;
use Throwable;

/**
 * Klien HTTP ke API internal (`/api/mobile/*` dan `/api/admin/*`).
 * Controller admin web tidak mengakses DB; hanya memanggil API lewat kelas ini.
 */
class ApiClient
{
    private CURLRequest $client;

    public function __construct(?CURLRequest $client = null)
    {
        $this->client = $client ?? Services::curlrequest([
            'timeout'     => 30,
            'http_errors' => false,
        ]);
    }

    /**
     * @param array<string, scalar|null> $formData
     *
     * @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
     */
    public function postMobile(string $method, array $formData = []): array
    {
        helper(['url']);

        return $this->post('api/mobile/' . ltrim($method, '/'), $formData);
    }

    /**
     * @param array<string, scalar|null> $formData
     *
     * @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
     */
    public function postMobileWithToken(string $method, string $token, array $formData = []): array
    {
        $formData['token'] = $token;

        return $this->postMobile($method, $formData);
    }

    /**
     * GET ke `/api/admin/{path}` — token ditambahkan ke query string.
     *
     * @param array<string, scalar|null> $query
     *
     * @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
     */
    public function getAdmin(string $path, ?string $token, array $query = []): array
    {
        helper(['url']);
        if ($token !== null && $token !== '') {
            $query['token'] = $token;
        }

        $forward = $this->adminInternalHeaders();
        if (($forward['Cookie'] ?? '') !== '' && session_status() === PHP_SESSION_ACTIVE) {
            session_write_close();
        }

        return $this->get('api/admin/' . ltrim($path, '/'), $query, $forward);
    }

    /**
     * POST form ke `/api/admin/{path}`.
     *
     * @param array<string, scalar|null> $formData
     *
     * @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
     */
    public function postAdmin(string $path, ?string $token, array $formData = []): array
    {
        helper(['url']);
        if ($token !== null && $token !== '') {
            $formData['token'] = $token;
        }

        $forward = $this->adminInternalHeaders();
        if (($forward['Cookie'] ?? '') !== '' && session_status() === PHP_SESSION_ACTIVE) {
            session_write_close();
        }

        return $this->post('api/admin/' . ltrim($path, '/'), $formData, $forward);
    }

    /**
     * GET generik (relatif terhadap root site, mis. `api/admin/pegawai`).
     *
     * @param array<string, scalar|null> $query
     * @param array<string, string>     $extraHeaders
     *
     * @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
     */
    public function get(string $path, array $query = [], array $extraHeaders = []): array
    {
        helper(['url']);
        $url = base_url(ltrim($path, '/'));

        $base = $this->emptyResult();

        $headers = array_merge(
            ['Accept' => 'application/json'],
            $extraHeaders
        );

        try {
            $response = $this->client->get($url, [
                'query'   => $query,
                'headers' => $headers,
            ]);
        } catch (Throwable $e) {
            $base['error'] = $e->getMessage();

            return $base;
        }

        return $this->fillFromResponse($base, $response->getStatusCode(), $response->getBody());
    }

    /**
     * POST `application/x-www-form-urlencoded`.
     *
     * @param array<string, scalar|null> $formData
     * @param array<string, string>      $extraHeaders
     *
     * @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
     */
    public function post(string $path, array $formData = [], array $extraHeaders = []): array
    {
        helper(['url']);
        $url = base_url(ltrim($path, '/'));

        $base = $this->emptyResult();

        $headers = array_merge(
            ['Accept' => 'application/json'],
            $extraHeaders
        );

        try {
            $response = $this->client->post($url, [
                'form_params' => $formData,
                'headers'     => $headers,
            ]);
        } catch (Throwable $e) {
            $base['error'] = $e->getMessage();

            return $base;
        }

        return $this->fillFromResponse($base, $response->getStatusCode(), $response->getBody());
    }

    /**
     * Teruskan cookie browser ke hit internal `/api/admin/*` agar sesi panel sama.
     *
     * @return array<string, string>
     */
    private function adminInternalHeaders(): array
    {
        $cookie = Services::request()->getHeaderLine('Cookie');

        return $cookie !== '' ? ['Cookie' => $cookie] : [];
    }

    /**
     * @param array<string, mixed>|null $json
     */
    public static function isSuccess(?array $json): bool
    {
        return is_array($json) && (int) ($json['status'] ?? 0) === 1;
    }

    /**
     * @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
     */
    private function emptyResult(): array
    {
        return [
            'transport_ok' => false,
            'http_code'    => 0,
            'json'         => null,
            'error'        => null,
            'raw'          => '',
        ];
    }

    /**
     * @param array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string} $base
     *
     * @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
     */
    private function fillFromResponse(array $base, int $statusCode, string $body): array
    {
        $base['http_code']    = $statusCode;
        $base['raw']         = $body;
        $base['transport_ok'] = $statusCode === 200;

        $decoded = json_decode($body, true);
        if (! is_array($decoded)) {
            $base['error'] = 'Respons bukan JSON objek';

            return $base;
        }

        $base['json'] = $decoded;

        return $base;
    }
}
