<?php

declare(strict_types=1);

namespace Kreait\Firebase\Auth;

use DateInterval;
use DateTimeImmutable;
use DateTimeInterface;
use Google\Auth\SignBlobInterface;
use Kreait\Firebase\Exception\Auth\AuthError;
use Kreait\Firebase\Util\DT;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Token\Parser;

/**
 * @internal
 */
final readonly class CustomTokenViaGoogleCredentials
{
    private JoseEncoder $encoder;

    private Parser $parser;

    public function __construct(private SignBlobInterface $signer, private ?string $tenantId = null)
    {
        $this->encoder = new JoseEncoder();
        $this->parser = new Parser($this->encoder);
    }

    /**
     * @param non-empty-string $uid
     * @param array<non-empty-string, mixed> $claims
     *
     * @throws AuthError
     */
    public function createCustomToken(string $uid, ?array $claims = null, ?DateTimeInterface $expiresAt = null): Token
    {
        $claims ??= [];

        $now = new DateTimeImmutable();
        $expiresAt = ($expiresAt instanceof DateTimeInterface)
            ? DT::toUTCDateTimeImmutable($expiresAt)
            : $now->add(new DateInterval('PT1H'));

        $header = ['typ' => 'JWT', 'alg' => 'RS256'];
        $payload = [
            'iss' => $this->signer->getClientName(),
            'sub' => $this->signer->getClientName(),
            'aud' => 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
            'iat' => $now->getTimestamp(),
            'exp' => $expiresAt->getTimestamp(),
            'uid' => $uid,
        ];

        if ($this->tenantId !== null) {
            $payload['tenant_id'] = $this->tenantId;
        }

        if ($claims !== []) {
            $payload['claims'] = $claims;
        }

        $base64UrlHeader = $this->base64EncodeArray($header);
        $base64UrlPayload = $this->base64EncodeArray($payload);

        $signature = $this->signer->signBlob($base64UrlHeader.'.'.$base64UrlPayload);
        $signature = str_replace(['=', '+', '/'], ['', '-', '_'], $signature);

        return $this->parser->parse(sprintf('%s.%s.%s', $base64UrlHeader, $base64UrlPayload, $signature));
    }

    /**
     * @param array<mixed> $array
     */
    private function base64EncodeArray(array $array): string
    {
        return $this->encoder->base64UrlEncode($this->encoder->jsonEncode($array));
    }
}
