<?php

namespace AuthStack\Driver\AUDGeneric\App\REST;

use DB;
use Exception;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\GuzzleException;

abstract class BaseRESTModel
{
    protected $httpClient;
    protected $driverName;

    protected function getConfig()
    {
        $driver = DB::table('drivers')->where('title', $this->driverName)
            ->first();

        $config = DB::table('drivers_connections')->where('driver_id', $driver->id)
            ->first();

        return json_decode($config->configuration, true);
    }

    public function getAuthSourceId(){
        $driver = DB::table('drivers')->where('title', $this->driverName)
            ->first();

        $config = DB::table('drivers_connections')->where('driver_id', $driver->id)
            ->first();

        return $config->id;
    }

    /**
     * @param string $section
     * @return Route
     */
    protected function getRoute(string $section): Route
    {
        $arr = $this->getConfig();
        $config = $arr[$section];

        return Route::fromArray($config);
    }

    /**
     * @return Client
     */
    public function getHttpClient(): Client
    {
        if (!$this->httpClient instanceof Client) {
            $this->httpClient = new Client();
        }

        return $this->httpClient;
    }

    /**
     * @param string $section
     * @param array|null $form_params
     * @param array|null $path_params
     * @param array|null $get_params
     * @param array|null $headers
     * @return array
     * @throws GuzzleException
     * @throws Exception
     */
    protected function json(
        string $section,
        ?array $form_params = null,
        ?array $path_params = null,
        ?array $get_params = null,
        ?array $headers = null
    ): array {
        if (empty($headers)) {
            $headers = [];
        }

        // This "tells" laravel to respond with json, not html via blades
        $headers['Accept'] = 'application/json';

        $result = $this->request($section, $form_params, $path_params, $get_params, $headers);

        $json = @json_decode($result->getBody(), true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception(json_last_error_msg(), json_last_error());
        }

        return $json;
    }

    /**
     * @param string $cfg_section
     * @param array|null $form_params
     * @param array|null $path_params
     * @param array|null $get_params
     * @param array|null $headers
     * @return mixed|ResponseInterface
     * @throws GuzzleException
     */
    protected function request(
        string $cfg_section,
        array $form_params = null,
        array $path_params = null,
        array $get_params = null,
        ?array $headers = null
    ) {
        $client = $this->getHttpClient();
        $route = $this->getRoute($cfg_section);
        $method = $route->getMethod();
        $path = $route->getPath($path_params);
        $url = sprintf("%s%s", $route->getUrl(), $path);

        if (empty($headers)) {
            $headers = [];
        }

        $options = [
            // Verify SSL true / false. For domains with no CA certificate, set this to false in config to make it work locally
            'verify' => $route->verifySSL(),

            // Authorization header (Bearer token)
            'headers' => $headers,

            // Form (body) parameters
            'form_params' => $form_params,

            // Querystring
            'query' => $get_params
        ];

        $res = $client->request($method, $url, $options);

        return $res;
    }

    /**
     * @param callable $callback
     * @param string $nextException
     * @param callable|null $afterException
     * @return mixed
     * @throws Exception
     */
    protected function remote(callable $callback, $nextException = Exception::class, callable $afterException = null)
    {
        try {
            return $callback();
        } catch (Exception $e) {
            // Fire LogErrors event
            // event(new LogErrors($e, LogErrors::LOGGER_C2MS));

            // Useful for error logging
            if (is_callable($afterException)) {
                $exception = $this->getExceptionFromResponse($e, $nextException);

                $afterException($exception);
            }

            $this->handleException($e, $nextException);
        }
    }

    /**
     * @param Exception $e
     * @param $nextException
     * @return Exception
     */
    protected function getExceptionFromResponse(Exception $e, $nextException): Exception
    {
        if (method_exists($e, 'getResponse')) {
            $response = $e->getResponse();

            if ($this->isJson($response)) {
                $body = @json_decode($e->getResponse()->getBody(), true);

                $message = isset($body) && is_array($body) ? $body : $e->getResponse()->getBody()->getContents();

                return new $nextException($message, (int)$e->getCode());
            }
        }

        return new $nextException(['message' => 'Uknown error during transfer'], 500);
    }

    /**
     * @param Exception $e
     * @param $nextException
     * @throws Exception
     */
    protected function handleException(Exception $e, $nextException): void
    {
        if (method_exists($e, 'getResponse')) {
            $response = $e->getResponse();

            // Response is empty if we query nonexistent URI or otherwise unreachable destination
            if (empty($e->getResponse())) {
                throw new Exception("Unable to communicate with AUD (destination unreachable)", 408);
            }

            if ($this->isJson($response)) {
                $body = @json_decode($e->getResponse()->getBody(), true);

                $message = isset($body) && is_array($body) ? $body : $e->getResponse()->getBody()->getContents();

                throw new $nextException($e->getResponse()->getBody(), (int)$e->getCode());
            }

            $error = !empty($e->getResponse()->getBody()->getContents()) ? $e->getResponse()->getBody()->getContents() : $e->getMessage();

            throw new Exception($error, $e->getCode());
        }

        throw new Exception($e->getMessage(), $e->getCode());
    }

    /**
     * @param ResponseInterface $response
     * @return bool
     */
    protected function isJson(ResponseInterface $response)
    {
        $result = false;

        if ($response->hasHeader('content-type')) {
            $contentType = $response->getHeader('content-type');

            if (is_string($contentType)) {
                $result = 0 === strcmp($contentType, 'application/json');
            }

            if (is_array($contentType)) {
                foreach ($contentType as $ctype) {
                    $result = 0 === strcmp($ctype, 'application/json');

                    if ($result) {
                        break;
                    }
                }
            }
        }

        return $result;
    }
}
