<?php


namespace App\Model\Services;


use App\Model\Exceptions\BattleNet\AuthorizationRequiredException;
use App\Model\Exceptions\BattleNet\InvalidRegionException;
use App\Model\Exceptions\BattleNet\InvalidStateException;
use App\Model\Crates\BattleNetProfile;
use Bitbang\Http\Clients\CurlClient;
use Bitbang\Http\Request;
use League\OAuth2\Client\Provider\GenericProvider;
use League\OAuth2\Client\Token\AccessToken;
use Nette\Http\Request as NetteHttpRequest;
use Nette\Http\Session;
use Nette\Http\SessionSection;

class BattleNetApiService
{
    /** @var Session */
    protected $session;

    /** @var  SessionSection */
    protected $sessionSection;

    /** @var  NetteHttpRequest */
    protected $request;

    protected $key;
    protected $secret;

    /**
     * BattleNetApiService constructor.
     * @param $key
     * @param $secret
     */
    public function __construct($key, $secret)
    {
        $this->key = $key;
        $this->secret = $secret;
    }

    /**
     * @param Session $session
     */
    public function injectSession(Session $session)
    {
        $this->session = $session;
        $this->sessionSection = $this->session->getSection('battleNet');
    }

    public function injectHttpRequest(NetteHttpRequest $request)
    {
        $this->request = $request;
    }

    /**
     * @return mixed
     * @throws AuthorizationRequiredException
     * @throws InvalidStateException
     */
    public function retrieveAuthorizationCode()
    {
        $code = $this->request->getQuery('code');
        if ($code === NULL) {
            throw new AuthorizationRequiredException();
        }

        $state = $this->request->getQuery('state');
        if (!$state || $state !== $this->sessionSection->oauth2State) {
            unset($this->sessionSection->oauth2State);
            throw new InvalidStateException();
        }

        return $code;
    }

    /**
     * @param $backlink
     * @param string $region
     * @return string
     */
    public function getAuthorizationUrl($backlink, $region = 'eu')
    {
        $provider = $this->getProvider($backlink, $region);
        $url = $provider->getAuthorizationUrl();
        $this->sessionSection->oauth2State = $provider->getState();
        return "{$url}&scope=wow.profile+sc2.profile";
    }

    /**
     * @param $backlink
     * @param $region
     * @param $code
     * @return AccessToken
     */
    public function retrieveAccessToken($backlink, $region, $code)
    {
        $provider = $this->getProvider($backlink, $region);
        return $provider->getAccessToken('authorization_code', array('code' => $code));
    }

    /**
     * @param $backlink
     * @param $region
     * @param $accessToken
     * @return BattleNetProfile
     */
    public function retrieveUserAccount($backlink, $region, $accessToken)
    {
        if (!$accessToken instanceof AccessToken) {
            $accessToken = new AccessToken(array('access_token' => $accessToken));
        }

        $result = $this->getProvider($backlink, $region)->getResourceOwner($accessToken);
        return new BattleNetProfile($result->toArray());
    }

    public function retrieveStarCraftProfile($region, $accessToken)
    {
        $url = $this->getApiHost($region) . "sc2/profile/user?access_token={$accessToken}";

        return $this->retrieveData($url);
    }

    public function retrieveWarCraftProfile($region, $accessToken)
    {
        $url = $this->getApiHost($region) . "wow/user/characters?access_token={$accessToken}";

        return $this->retrieveData($url);
    }

    public function retrieveCharacterProfile($region, $realm, $characterName)
    {
        $url = $this->assembleUrl($region, "wow/character/{$realm}/{$characterName}");

        return $this->retrieveData($url);
    }

    public function retrieveRealmStatus(string $region)
    {
        $url = $this->assembleUrl($region, '/wow/realm/status');

        return $this->retrieveData($url);
    }

    public function retrieveCharacterClasses(string $region)
    {
        $url = $this->assembleUrl($region, '/wow/data/character/classes');

        return $this->retrieveData($url);
    }

    public function retrieveRaces(string $region)
    {
        $url = $this->assembleUrl($region, '/wow/data/character/races');

        return $this->retrieveData($url);
    }

    public function retrieveInstances(string $region)
    {
        $url = $this->assembleUrl($region, '/wow/zone/');

        return $this->retrieveData($url);
    }

    protected function retrieveData($url)
    {
        $httpRequest = new Request('GET', $url);
        $client = new CurlClient(array(CURLOPT_SSL_VERIFYPEER => false));
        $httpResponse = $client->process($httpRequest);
        // todo: data can be invalid
//        if ($httpResponse->getCode() !== 200) {
//            throw new ServerResponseException();
//        }
        return json_decode($httpResponse->getBody());
    }

    protected function assembleUrl($region, $qry)
    {
        $url = $this->getApiHost($region) . $qry;
        $url .= '?locale=' . $this->getLocale($region);
        $url .= '&apikey=' . $this->key;

        return $url;
    }

    protected function getProvider($backlink, $region)
    {
        $params = array();
        $params['clientId'] = $this->key;
        $params['clientSecret'] = $this->secret;
        $params['redirectUri'] = $backlink;
        $params['urlAuthorize'] = $this->getOAuthHost($region) . "oauth/authorize";
        $params['urlAccessToken'] = $this->getOAuthHost($region) . "oauth/token";
        $params['urlResourceOwnerDetails'] = $this->getApiHost($region) . "account/user";

        return new GenericProvider($params);
    }

    protected function getApiHost($region)
    {
        if ($region === 'cn') {
            return 'https://api.battlenet.com.cn/';
        }

        return "https://{$region}.api.battle.net/";
    }

    protected function getOAuthHost($region)
    {
        if ($region === 'cn') {
            return 'https://www.battlenet.com.cn/';
        }

        return "https://{$region}.battle.net/";
    }

    protected function getLocale($region)
    {
        switch ($region) {
            case 'us':
                return 'en_US';
            case 'eu':
                return 'en_GB';
            case 'kr':
                return 'ko_KR';
            case 'tw':
                return 'zh_TW';
            case 'cn':
                return 'zh_CN';
            case 'sea':
                return 'en_US';
            default:
                throw new InvalidRegionException();
        }
    }
}