<?php

namespace idoit\Module\JDisc\Graphql;

use Exception;
use GraphQL\Client;
use idoit\Module\JDisc\Graphql\Mutation\Authentication\Login;
use idoit\Module\JDisc\Graphql\Mutation\Authentication\Logout;
use idoit\Module\JDisc\Graphql\Mutation\Authentication\RefreshToken;
use idoit\Module\JDisc\Graphql\Type\StringType;
use isys_application;
use isys_format_json;
use isys_helper_crypt;
use isys_jdisc_dao;

class Connector
{
    /**
     * @var Config|null
     */
    private ?Config $config = null;

    /**
     * @var Token|null
     */
    private ?Token $token = null;

    /**
     * @var Connector
     */
    private static $instance = null;

    /**
     * @param Token|null $token
     */
    public function setToken(?Token $token): void
    {
        $this->token = $token;
    }

    /**
     * @param array $headers
     *
     * @return Client
     */
    private function getClient(array $headers = ['Content-Type' => 'application/json']): Client
    {
        return new Client(
            $this->config->getUrl(),
            $headers,
            ["verify" => false]
        );
    }

    /**
     * @param int|null $serverId
     *
     * @return Connector|null
     */
    public static function instance(?int $serverId)
    {
        if (self::$instance === null) {
            $instance = new self();
            $server = isys_jdisc_dao::instance(isys_application::instance()->container->get('database'))
                ->get_jdisc_discovery_data($serverId)
                ->get_row();
            $table = 'isys_jdisc_db';

            $config = Config::factory(
                $server["{$table}__host"],
                $server["{$table}__discovery_username"],
                isys_helper_crypt::decrypt($server["{$table}__discovery_password"]),
                $server["{$table}__discovery_port"],
                $server["{$table}__discovery_protocol"]
            );
            self::$instance = $instance->setConfig($config);
        }
        return self::$instance;
    }

    /**
     * @param Config|null $config
     */
    public function setConfig(?Config $config): Connector
    {
        $this->config = $config;
        return $this;
    }

    /**
     * @return void
     */
    public function disconnect()
    {
        $client = $this->getClient($this->getQueryHeader());
        $logout = new Logout();
        $logout->setParameters([
            new StringType('accessToken', $this->token->getAccessToken())
        ]);
        $client->runQuery($logout->getQuery(), true, $logout->getFlattenParameters());
    }

    /**
     * @return void
     */
    public function connect()
    {
        if ($this->token !== null) {
            return;
        }

        if (empty($this->config->getUsername()) || empty($this->config->getPassword())) {
            throw new Exception('Username or Password is not set for JDisc Discovery');
        }
        $url = $this->config->getUrl();
        $headers = [
            'Content-Type' => 'application/json'
        ];

        try {
            $client = $this->getClient();

            $login = new Login();
            $login->setParameters([
                new StringType('login', $this->config->getUsername()),
                new StringType('password', $this->config->getPassword())
            ]);

            $this->config->setUsername('');
            $this->config->setPassword('');

            $data = Login::getDataFromResults(
                $client->runQuery($login->getQuery(), true, $login->getFlattenParameters())
            );

            self::$instance->setToken(new Token(
                $data['accessToken'],
                $data['refreshToken']
            ));
        } catch(Exception $e) {
            // throw exception connection could not be established
            throw new Exception($e->getMessage());
        }
    }

    /**
     * @return string[]
     */
    private function getQueryHeader(): array
    {
        return [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . $this->token->getAccessToken()
        ];
    }

    /**
     * @param AbstractGraphql $query
     *
     * @return array
     */
    public function query(AbstractGraphql $query): array
    {
        $client = $this->getClient($this->getQueryHeader());
        $data = [];
        try {
            $result = $client->runQuery($query->getQuery(), true, $query->getFlattenParameters());

            if ($query instanceof ResponseInterface) {
                return $query::getDataFromResults($result);
            }
        } catch (Exception $e) {
            throw new Exception("Could not send request with message: " . get_class($query));
        }

        return [];
    }
}