<?php

namespace App\Services;

use App\Models\User;
use Illuminate\Support\Facades\Cache;

/**
 * JWT Service
 * 
 * Handles JWT token generation, validation, and refresh token management.
 */
class JwtService
{
    /**
     * Access token TTL in seconds (60 minutes)
     */
    private const ACCESS_TOKEN_TTL = 3600;

    /**
     * Refresh token TTL in seconds (7 days)
     */
    private const REFRESH_TOKEN_TTL = 604800;

    /**
     * Generate a new access token for a user
     *
     * @param User $user
     * @return string
     */
    public function generateAccessToken(User $user): string
    {
        $payload = [
            'iss' => config('app.url', 'headroom'),
            'sub' => $user->id,
            'iat' => time(),
            'exp' => time() + self::ACCESS_TOKEN_TTL,
            'role' => $user->role,
            'permissions' => $this->getPermissions($user->role),
            'jti' => $this->generateTokenId(),
        ];

        return $this->encodeJWT($payload);
    }

    /**
     * Generate a new refresh token for a user
     *
     * @param User $user
     * @return string
     */
    public function generateRefreshToken(User $user): string
    {
        $token = $this->generateSecureToken();
        $key = $this->getRefreshTokenKey($token);
        
        Cache::put($key, $user->id, self::REFRESH_TOKEN_TTL);

        return $token;
    }

    /**
     * Get user ID from a refresh token
     *
     * @param string $token
     * @return string|null
     */
    public function getUserIdFromRefreshToken(string $token): ?string
    {
        return Cache::get($this->getRefreshTokenKey($token));
    }

    /**
     * Invalidate a refresh token
     *
     * @param string $token
     * @param string|null $userId
     * @return void
     */
    public function invalidateRefreshToken(string $token, ?string $userId = null): void
    {
        Cache::forget($this->getRefreshTokenKey($token));
    }

    /**
     * Validate and decode a JWT token
     *
     * @param string $token
     * @return array|null Returns payload array or null if invalid
     */
    public function validateToken(string $token): ?array
    {
        $parts = explode('.', $token);
        
        if (count($parts) !== 3) {
            return null;
        }

        [$header, $payload, $signature] = $parts;

        // Verify signature
        $expectedSignature = $this->createSignature($header, $payload);
        
        if (! hash_equals($expectedSignature, $signature)) {
            return null;
        }

        // Decode payload
        $payloadData = $this->base64UrlDecode($payload);
        $payloadArray = json_decode($payloadData, true);

        if (! is_array($payloadArray)) {
            return null;
        }

        // Check expiration
        if (isset($payloadArray['exp']) && $payloadArray['exp'] < time()) {
            return null;
        }

        return $payloadArray;
    }

    /**
     * Extract claims from a JWT token
     *
     * @param string $token
     * @return array|null
     */
    public function extractClaims(string $token): ?array
    {
        return $this->validateToken($token);
    }

    /**
     * Get token expiration time
     *
     * @return int
     */
    public function getAccessTokenTTL(): int
    {
        return self::ACCESS_TOKEN_TTL;
    }

    /**
     * Get refresh token expiration time
     *
     * @return int
     */
    public function getRefreshTokenTTL(): int
    {
        return self::REFRESH_TOKEN_TTL;
    }

    /**
     * Get permissions for a role
     *
     * @param string $role
     * @return array
     */
    public function getPermissions(string $role): array
    {
        return match ($role) {
            'superuser' => [
                'manage_users',
                'manage_team_members',
                'manage_projects',
                'manage_allocations',
                'manage_actuals',
                'view_reports',
                'configure_system',
                'view_audit_logs',
            ],
            'manager' => [
                'manage_projects',
                'manage_allocations',
                'manage_actuals',
                'view_reports',
                'manage_team_members',
            ],
            'developer' => [
                'manage_actuals',
                'view_own_allocations',
                'view_own_actuals',
                'log_hours',
            ],
            'top_brass' => [
                'view_reports',
                'view_allocations',
                'view_actuals',
                'view_capacity',
            ],
            default => [],
        };
    }

    /**
     * Encode a JWT token
     *
     * @param array $payload
     * @return string
     */
    private function encodeJWT(array $payload): string
    {
        $header = $this->base64UrlEncode(json_encode(['typ' => 'JWT', 'alg' => 'HS256']));
        $payload = $this->base64UrlEncode(json_encode($payload));
        $signature = $this->createSignature($header, $payload);

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

    /**
     * Create a signature for JWT
     *
     * @param string $header
     * @param string $payload
     * @return string
     */
    private function createSignature(string $header, string $payload): string
    {
        $signature = hash_hmac('sha256', $header . '.' . $payload, config('app.key'), true);
        
        return $this->base64UrlEncode($signature);
    }

    /**
     * Generate a cryptographically secure random token
     *
     * @return string
     */
    private function generateSecureToken(): string
    {
        return bin2hex(random_bytes(32));
    }

    /**
     * Generate a unique token ID
     *
     * @return string
     */
    private function generateTokenId(): string
    {
        return uniqid('token_', true);
    }

    /**
     * Get cache key for refresh token
     *
     * @param string $token
     * @return string
     */
    private function getRefreshTokenKey(string $token): string
    {
        return "refresh_token:{$token}";
    }

    /**
     * Base64URL encode
     *
     * @param string $data
     * @return string
     */
    private function base64UrlEncode(string $data): string
    {
        return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
    }

    /**
     * Base64URL decode
     *
     * @param string $data
     * @return string
     */
    private function base64UrlDecode(string $data): string
    {
        $padding = 4 - (strlen($data) % 4);
        if ($padding !== 4) {
            $data .= str_repeat('=', $padding);
        }
        
        return base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
    }
}
