<?php

namespace idoit\Module\SyneticsJdisc\Graphql;

use Exception;
use GraphQL\Client;
use idoit\Module\SyneticsJdisc\Graphql\Mutation\Authentication\Login;
use idoit\Module\SyneticsJdisc\Graphql\Mutation\Authentication\Logout;
use idoit\Module\SyneticsJdisc\Graphql\Type\StringType;
use isys_application;
use isys_cmdb_dao_category_g_file;
use isys_cmdb_dao_category_s_file;
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;

    public function getToken(): ?Token
    {
        return $this->token;
    }

    /**
     * @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
    {
        $options = [
            'verify' => $this->config->getCertificate() ?? false
        ];

        return new Client(
            $this->config->getUrl(),
            $headers,
            $options
        );
    }

    /**
     * @param int|null $serverId
     *
     * @return Connector|null
     */
    public static function instance(?int $serverId)
    {
        if (self::$instance[$serverId] === 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(
                (string) trim($server["{$table}__host"]),
                (string) trim($server["{$table}__discovery_username"]),
                (string) isys_helper_crypt::decrypt($server["{$table}__discovery_password"]),
                (int) $server["{$table}__discovery_port"],
                (string) trim($server["{$table}__discovery_protocol"])
            );

            if (!empty($server["{$table}__discovery_tls_certificate"])) {
                $certificate = isys_cmdb_dao_category_g_file::getDownloadPath(
                    isys_cmdb_dao_category_s_file::instance(isys_application::instance()->container->get('database'))
                        ->get_file_by_obj_id((int) $server["{$table}__discovery_tls_certificate"])
                        ->get_row()['isys_file_version__isys_file_physical__id']
                );

                if (!file_exists($certificate)) {
                    throw new Exception(
                        "The given TLS certificate file (object #{$server["{$table}__discovery_tls_certificate"]}) does not exist!"
                    );
                }

                $config->setCertificate($certificate);
            }

            self::$instance[$serverId] = $instance->setConfig($config);
        }
        return self::$instance[$serverId];
    }

    /**
     * @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();

        $logoutParameter = new StringType('accessToken', $this->token->getAccessToken());
        $logoutParameter->setRequired(true);

        $logout->setParameters([
            $logoutParameter,
        ]);
        $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();

            $loginParameter = new StringType('login', $this->config->getUsername());
            $loginParameter->setRequired(true);

            $passwordParameter = new StringType('password', $this->config->getPassword());
            $passwordParameter->setRequired(true);

            $login->setParameters([
                $loginParameter,
                $passwordParameter,
            ]);

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

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

            if (empty($data['accessToken']) && $data['status'] === 'BAD_USERNAME_OR_PASSWORD') {
                $language = isys_application::instance()->container->get('language');
                throw new GraphqlLoginException($language->get('LC__MODULE__JDISC__GRAPHQL__LOGIN_FAILED'));
            }

            $this->setToken(new Token(
                $data['accessToken'],
                $data['refreshToken']
            ));
        } catch (\Throwable $e) {
            throw $e;
        }
    }

    /**
     * @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)
                . PHP_EOL . $e->getMessage()
            );
        }

        return [];
    }
}
