<?php

declare(strict_types=1);

/*
 * Copyright (c) 2017-2023 François Kooman <fkooman@tuxed.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

namespace fkooman\OAuth\Server;

use DateTimeImmutable;
use fkooman\OAuth\Server\Exception\InvalidGrantException;
use RangeException;

class AuthorizationCode
{
    public const TOKEN_TYPE = 'authorization_code';

    private string $tokenId;
    private string $authKey;
    private string $userId;
    private string $clientId;
    private Scope $scope;
    private DateTimeImmutable $expiresAt;
    private DateTimeImmutable $authorizationExpiresAt;
    private string $redirectUri;
    private string $codeChallenge;

    public function __construct(string $tokenId, string $authKey, string $userId, string $clientId, Scope $scope, DateTimeImmutable $expiresAt, DateTimeImmutable $authorizationExpiresAt, string $redirectUri, string $codeChallenge)
    {
        $this->tokenId = $tokenId;
        $this->authKey = $authKey;
        $this->userId = $userId;
        $this->clientId = $clientId;
        $this->scope = $scope;
        $this->expiresAt = $expiresAt;
        $this->authorizationExpiresAt = $authorizationExpiresAt;
        $this->redirectUri = $redirectUri;
        $this->codeChallenge = $codeChallenge;
    }

    public static function fromJson(string $jsonString): self
    {
        $jsonData = Json::decode($jsonString);

        try {
            if (self::TOKEN_TYPE !== Extractor::requireString($jsonData, 'type')) {
                throw new InvalidGrantException(sprintf('"type" MUST be "%s"', self::TOKEN_TYPE));
            }

            return new self(
                Extractor::requireString($jsonData, 'token_id'),
                Extractor::requireString($jsonData, 'auth_key'),
                Extractor::requireString($jsonData, 'user_id'),
                Extractor::requireString($jsonData, 'client_id'),
                new Scope(Extractor::requireString($jsonData, 'scope')),
                new DateTimeImmutable(Extractor::requireString($jsonData, 'expires_at')),
                new DateTimeImmutable(Extractor::requireString($jsonData, 'authorization_expires_at')),
                Extractor::requireString($jsonData, 'redirect_uri'),
                Extractor::requireString($jsonData, 'code_challenge')
            );
        } catch (RangeException $e) {
            throw new InvalidGrantException($e->getMessage());
        }
    }

    public function tokenId(): string
    {
        return $this->tokenId;
    }

    public function authKey(): string
    {
        return $this->authKey;
    }

    public function userId(): string
    {
        return $this->userId;
    }

    public function clientId(): string
    {
        return $this->clientId;
    }

    public function scope(): Scope
    {
        return $this->scope;
    }

    public function expiresAt(): DateTimeImmutable
    {
        return $this->expiresAt;
    }

    public function authorizationExpiresAt(): DateTimeImmutable
    {
        return $this->authorizationExpiresAt;
    }

    public function redirectUri(): string
    {
        return $this->redirectUri;
    }

    public function codeChallenge(): string
    {
        return $this->codeChallenge;
    }

    public function json(): string
    {
        return Json::encode(
            [
                'type' => self::TOKEN_TYPE,
                'token_id' => $this->tokenId,
                'auth_key' => $this->authKey,
                'user_id' => $this->userId,
                'client_id' => $this->clientId,
                'scope' => (string) $this->scope,
                'expires_at' => $this->expiresAt->format(DateTimeImmutable::ATOM),
                'authorization_expires_at' => $this->authorizationExpiresAt->format(DateTimeImmutable::ATOM),
                'redirect_uri' => $this->redirectUri,
                'code_challenge' => $this->codeChallenge,
            ]
        );
    }
}
