<?php

namespace App\Services;

use Illuminate\Support\Arr;
use Illuminate\Support\Facades\URL;
use JsonException;
use RuntimeException;

class SignedDownloadService
{
    public function sign(array $claims): string
    {
        $payload = $this->base64UrlEncode(json_encode($claims, JSON_THROW_ON_ERROR));
        $signature = $this->base64UrlEncode(hash_hmac('sha256', $payload, $this->key(), true));

        return $payload.'.'.$signature;
    }

    public function verify(string $token): array
    {
        [$payload, $signature] = array_pad(explode('.', $token, 2), 2, '');
        $expected = $this->base64UrlEncode(hash_hmac('sha256', $payload, $this->key(), true));

        if (! hash_equals($expected, $signature)) {
            throw new RuntimeException('Invalid download token.');
        }

        try {
            $claims = json_decode($this->base64UrlDecode($payload), true, flags: JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            throw new RuntimeException('Invalid download token payload.');
        }

        if (! is_array($claims) || Arr::get($claims, 'expires_at') < now()->timestamp) {
            throw new RuntimeException('Download token has expired.');
        }

        return $claims;
    }

    public function url(string $token): string
    {
        return URL::to('/api/v1/downloads/'.rawurlencode($token));
    }

    private function key(): string
    {
        return (string) config('services.nusaclouds.download_signing_key', config('app.key'));
    }

    private function base64UrlEncode(string $value): string
    {
        return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
    }

    private function base64UrlDecode(string $value): string
    {
        return base64_decode(strtr($value, '-_', '+/')) ?: '';
    }
}
