<?php
namespace Misuzu\Twitter;

use RuntimeException;
use Misuzu\Config\IConfig;

class TwitterClient {
    public const SYSTEM_SCOPES = [
        'tweet.read', 'tweet.write',
        'users.read', 'offline.access',
        'follows.read', 'follows.write',
        'like.read', 'like.write',
    ];

    private const API_BASE = 'https://api.twitter.com';
    private const API_V2 = self::API_BASE . '/2';

    private const API_OAUTH2 = self::API_V2 . '/oauth2';
    private const API_OAUTH2_TOKEN = self::API_OAUTH2 . '/token';
    private const API_OAUTH2_REVOKE = self::API_OAUTH2 . '/revoke';

    private const API_TWEETS = self::API_V2 . '/tweets';

    public function __construct(
        private TwitterClientId $clientId,
        private TwitterAccessToken $accessToken
    ) {}

    public function getClientId(): TwitterClientId {
        return $this->clientId;
    }
    public function hasClientId(): bool {
        return $this->clientId->hasClientId();
    }

    public function getAccessToken(): TwitterAccessToken {
        return $this->accessToken;
    }
    public function hasAccessToken(): bool {
        return $this->accessToken->hasAccessToken();
    }
    public function hasRefreshToken(): bool {
        return $this->accessToken->hasRefreshToken();
    }

    public function authorise(array $scope, string $redirect): TwitterAuthorisation {
        return new TwitterAuthorisation($this->clientId, $scope, $redirect);
    }

    public function token(string $code, string $verifier, string $redirect): object {
        if(!$this->clientId->hasClientId())
            throw new RuntimeException('Need OAuth2 info in order to manage tokens.');

        $req = json_decode(self::request('POST', self::API_OAUTH2_TOKEN, [
            'Authorization: ' . (string)$this->clientId,
        ], [], [
            'code' => $code,
            'grant_type' => 'authorization_code',
            'code_verifier' => $verifier,
            'redirect_uri' => $redirect, // needed because????????
        ], false));

        if($req === false)
            return new RuntimeException('Unable to parse token response.');

        return $req;
    }

    public function authRefresh(): object {
        if(!$this->clientId->hasClientId())
            throw new RuntimeException('Need OAuth2 info in order to manage tokens.');
        if(!$this->accessToken->hasRefreshToken())
            throw new RuntimeException('There is no refresh token.');

        $req = json_decode(self::request('POST', self::API_OAUTH2_TOKEN, [
            'Authorization: ' . (string)$this->clientId,
        ], [], [
            'refresh_token' => $this->accessToken->getRefreshToken(),
            'grant_type' => 'refresh_token',
        ], false));

        if($req === false)
            return new RuntimeException('Unable to parse token response.');

        return $req;
    }

    public function authRevoke(): object {
        if(!$this->clientId->hasClientId())
            throw new RuntimeException('Need OAuth2 info in order to manage tokens.');
        if(!$this->accessToken->hasAccessToken())
            throw new RuntimeException('Cannot revoke an access token we do not have.');

        $req = json_decode(self::request('POST', self::API_OAUTH2_REVOKE, [
            'Authorization: ' . (string)$this->clientId,
        ], [], [
            'token' => $this->accessToken->getAccessToken(),
            'token_type_hint' => 'access_token',
        ], false));

        if($req === false)
            return new RuntimeException('Unable to parse token response.');

        return $req;
    }

    public function sendTweet(string $text): object {
        if(!$this->accessToken->hasAccessToken())
            throw new RuntimeException('Need access token in order to post Tweets.');

        $req = json_decode(self::request('POST', self::API_TWEETS, [
            'Authorization: ' . (string)$this->accessToken,
        ], [], [
            'text' => $text,
        ]));

        if($req === false)
            return new RuntimeException('Unable to parse Tweet response.');

        return $req;
    }

    public function request(
        string $method,
        string $uri,
        array $headers = [],
        array $queryFields = [],
        mixed $bodyFields = [],
        bool $bodyAsJson = true,
    ): string|bool {
        if(!empty($queryFields))
            $uri .= '?' . http_build_query($queryFields, '', null, PHP_QUERY_RFC3986);

        $curl = curl_init($uri);
        curl_setopt_array($curl, [
            CURLOPT_AUTOREFERER => true,
            CURLOPT_FAILONERROR => false,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_TCP_NODELAY => true,
            CURLOPT_HEADER => false,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TCP_FASTOPEN => true,
            CURLOPT_MAXREDIRS => 3,
            CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
            CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
            CURLOPT_TIMEOUT => 2,
            CURLOPT_USERAGENT => 'Misuzu TwitterClient/20230105',
        ]);

        if($method === 'GET')
            curl_setopt($curl, CURLOPT_HTTPGET, true);
        elseif($method === 'HEAD') {
            curl_setopt($curl, CURLOPT_HEADER, true);
            curl_setopt($curl, CURLOPT_NOBODY, true);
        } elseif($method === 'POST')
            curl_setopt($curl, CURLOPT_POST, true);
        else
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);

        if(!empty($bodyFields)) {
            if($bodyAsJson) {
                $headers[] = 'Content-Type: application/json';
                $bodyFields = json_encode($bodyFields);
            } elseif(is_array($bodyFields)) {
                $headers[] = 'Content-Type: application/x-www-form-urlencoded';
                $bodyFields = http_build_query($bodyFields, '', null, PHP_QUERY_RFC3986);
            }

            curl_setopt($curl, CURLOPT_POSTFIELDS, $bodyFields);
        }

        if(!empty($headers))
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

        $out = curl_exec($curl);
        curl_close($curl);

        return $out;
    }

    public static function create(IConfig $config): self {
        return new static(
            TwitterClientId::load($config->scopeTo('oauth2')),
            TwitterAccessToken::load($config->scopeTo('access'))
        );
    }
}
