<?php
declare(strict_types=1);
namespace Root\Core\Helper;

use Illuminate\Support\Facades\Http;

class InternalApi {

    protected string $baseUrl;
    protected string $apiKey;
    protected string $method = 'GET';
    protected string $uri = '';
    protected string $subDomain = '';
    protected array $data = [];
    protected array $files = [];
    protected array $headers = [];
    protected ?string $rollbackUri = null;
    protected $rollbackData = [];
    protected ?string $rollbackSubDomain = null;
    protected $rollbackDataCallback = null;
    protected array $lastResponse = [];

    public function __construct()
    {
        $this->baseUrl = CoreConfig('App.microserviceURL'); 
        $this->apiKey = CoreConfig('App.apiKey');
    }

    public function request(
        string $method,
        string $uri,
        array $data = [],
        array $files = [],
        array $header = [],
        string $subDomain = ''
    ): array 
    {
        $method = strtolower($method);
        $url = ($subDomain ? $subDomain . '.' : '') . $this->baseUrl . '/api/' . ltrim($uri, '/');

        $header['Accept'] = 'application/json';
        $header['X-Internal-Api-Key'] = $this->apiKey;

        $http = Http::withHeaders($header);

        try {
            if (!empty($files)) {

                foreach ($files as $key => $file) {

                    if ($file instanceof \Illuminate\Http\UploadedFile) {

                        $http = $http->attach(
                            $key,
                            file_get_contents($file->getRealPath()),
                            $file->getClientOriginalName()
                        );

                    }
                }

                $response = $http->asMultipart()->post($url, $data);

            } else {
                $response = $http->$method($url, $data);
            }

            if ($response->successful()) {
                $this->lastResponse = [
                    'success' => true,
                    'status' => $response->status(),
                    'data' => $response->json(),
                ];
            } else {
                $this->lastResponse = [
                    'success' => false,
                    'status' => $response->status(),
                    'error' => $this->parseError($response),
                    'raw' => $response->body(),
                ];
            }

        } catch (\Throwable $e) {
            $this->lastResponse = [
                'success' => false,
                'status' => 0,
                'exception' => true,
                'message' => $e->getMessage(),
                'trace' => config('app.debug') ? collect($e->getTrace())->take(5) : [],
            ];
        }

        return $this->lastResponse['data'] ?? $this->lastResponse;
    }

    protected function parseError($response)
    {
        try {
            $json = $response->json();
            if (is_array($json)) {
                return $json;
            }
        } catch (\Throwable $e) {
            //
        }

        return $response->body();
    }

    public function isSuccess(): bool
    {
        return
            ($this->lastResponse['success'] ?? false) === true &&
            in_array(($this->lastResponse['status'] ?? 0), [200, 201]) &&
            (
                !isset($this->lastResponse['data']['success']) ||
                $this->lastResponse['data']['success'] === true
            )
        ;
    }

    public function getResponse()
    {
        return $this->lastResponse['data'] ?? null;
    }

    public function getErrors():?string
    {

        if (!empty($this->lastResponse['data']['validationErrors']) && is_array($this->lastResponse['data']['validationErrors'])) {
            foreach ($this->lastResponse['data']['validationErrors'] as $fieldErrors) {
                if (!empty($fieldErrors) && is_array($fieldErrors)) {
                    return (string) $fieldErrors[0]; 
                }
            }
        }

        if (!empty($this->lastResponse['data']['runtimeErrors']) && is_array($this->lastResponse['data']['runtimeErrors'])) {
            foreach ($this->lastResponse['data']['runtimeErrors'] as $error) {
                if (!empty($error)) {
                    return (string) $error; 
                }
            }
        }

        return null;
    }

    public function getRawResponse(): array
    {
        return $this->lastResponse;
    }

    public function post(): self
    {
        $this->method = 'POST';
        return $this;
    }

    public function get(): self
    {
        $this->method = 'GET';
        return $this;
    }

    public function to(string $uri): self
    {
        $this->uri = $uri;
        return $this;
    }

    public function withSubDomain(string $subDomain): self
    {
        if (empty($subDomain)) 
            return $this;
        $this->subDomain = $subDomain;
        return $this;
    }

    public function data(array $data): self
    {
        $this->data = $data;
        return $this;
    }

    public function file(array $files): self
    {
        $this->files = $files;
        return $this;
    }

    public function withHeader(array $headers): self
    {
        $this->headers = array_merge($this->headers, $headers);
        return $this;
    }

    public function send(): array
    {
        
        $response = $this->request(
            $this->method,
            $this->uri,
            $this->data,
            $this->files,
            $this->headers,
            $this->subDomain
        );

        if ($this->rollbackUri && $this->rollbackDataCallback) {
            $callback = $this->rollbackDataCallback;
            $this->rollbackData = $callback($response, $this->data);
            $this->rollbackDataCallback = null;
        }
    
        return $response;
    }

    public function rollbackTo(string $uri, string $subDomain = ''): self
    {
        $this->rollbackUri = $uri;
        $this->rollbackSubDomain = $subDomain;
        return $this;
    }

    public function rollbackData(array|callable $data): self
    {
        $this->rollbackData = $data;
        return $this;
    }

    protected function prepareRollbackData(array $response, array $requestData): array
    {
        if (is_callable($this->rollbackData)) {
            return call_user_func($this->rollbackData, $response, $requestData);
        }

        return $this->rollbackData;
    }

    public function doRollback(): ?array
    {
        if (!$this->rollbackUri) 
            return null;
        
        $data = $this->prepareRollbackData(
            array_key_exists('data',$this->lastResponse) ? $this->lastResponse['data'] : [],
            $this->data
        );

        return $this->request(
            $this->method,
            $this->rollbackUri,
            $data,
            [],
            $this->headers,
            $this->subDomain
        );
    }


}
