<?php

namespace App\Http\Controllers;

use App\Models\ManagedEnvironment;
use App\Support\ManagedEnvironmentLinks;
use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class RbacDelegatedAuthController extends Controller
{
    public function start(Request $request): RedirectResponse
    {
        $tenantIdentifier = $request->string('tenant')->toString();
        $tenant = $tenantIdentifier
            ? ManagedEnvironment::query()->forTenant($tenantIdentifier)->firstOrFail()
            : ManagedEnvironment::current();

        $targetTenant = $tenantIdentifier ?: $tenant->graphTenantId();

        $clientId = config('graph.client_id');
        $redirectUri = route('admin.rbac.callback');

        abort_if(empty($clientId) || empty($redirectUri), Response::HTTP_BAD_GATEWAY, 'Graph client not configured');

        $state = Str::uuid()->toString();

        $request->session()->put('rbac_state', $state);
        $request->session()->put('rbac_tenant', $tenant->getKey());
        $returnTo = $this->sanitizeReturnPath(
            $request->string('return')->toString()
            ?: ManagedEnvironmentLinks::viewUrl($tenant)
        );
        $request->session()->put('rbac_return', $returnTo);

        $scopes = implode(' ', [
            'openid',
            'profile',
            'offline_access',
            'Directory.ReadWrite.All',
            'Group.ReadWrite.All',
            'DeviceManagementRBAC.ReadWrite.All',
            'DeviceManagementConfiguration.ReadWrite.All',
        ]);

        $url = "https://login.microsoftonline.com/{$targetTenant}/oauth2/v2.0/authorize?".http_build_query([
            'client_id' => $clientId,
            'response_type' => 'code',
            'redirect_uri' => $redirectUri,
            'response_mode' => 'query',
            'scope' => $scopes,
            'state' => $state,
        ]);

        return redirect()->away($url);
    }

    public function callback(Request $request): RedirectResponse
    {
        $expectedState = $request->session()->pull('rbac_state');
        $tenantId = $request->session()->pull('rbac_tenant');
        $returnTo = $request->session()->pull('rbac_return');

        abort_if(! $expectedState, Response::HTTP_FORBIDDEN, 'RBAC state missing');
        abort_if($expectedState !== $request->string('state')->toString(), Response::HTTP_FORBIDDEN, 'Invalid RBAC state');
        abort_if(! $tenantId, Response::HTTP_BAD_REQUEST, 'ManagedEnvironment context missing');

        /** @var ManagedEnvironment $tenant */
        $tenant = ManagedEnvironment::query()->findOrFail($tenantId);

        $code = $request->string('code')->toString();
        abort_if(empty($code), Response::HTTP_BAD_REQUEST, 'Authorization code missing');

        $tokens = $this->exchangeAuthorizationCode($code);

        if (empty($tokens['access_token'])) {
            abort(Response::HTTP_BAD_GATEWAY, 'Failed to exchange code for token');
        }

        $accessToken = $tokens['access_token'];
        $ttl = CarbonImmutable::now()->addMinutes(5);

        // Store token keyed by user id (preferred) and session id (fallback) to survive SPA refreshes.
        if (auth()->check()) {
            Cache::put($this->cacheKey($tenant, auth()->id(), null), $accessToken, $ttl);
        }

        Cache::put($this->cacheKey($tenant, auth()->id(), $request->session()->getId()), $accessToken, $ttl);

        $destination = $this->sanitizeReturnPath($returnTo) ?: ManagedEnvironmentLinks::viewUrl($tenant);

        return redirect()->to($destination);
    }

    /**
     * @return array{access_token:?string,refresh_token:?string,expires_in:?int}
     */
    private function exchangeAuthorizationCode(string $code): array
    {
        $response = Http::asForm()->post(sprintf(
            'https://login.microsoftonline.com/%s/oauth2/v2.0/token',
            config('graph.managed_environment_id', 'common')
        ), [
            'client_id' => config('graph.client_id'),
            'client_secret' => config('graph.client_secret'),
            'code' => $code,
            'grant_type' => 'authorization_code',
            'redirect_uri' => route('admin.rbac.callback'),
        ]);

        if ($response->failed()) {
            return [];
        }

        $json = $response->json() ?: [];

        return [
            'access_token' => $json['access_token'] ?? null,
            'refresh_token' => $json['refresh_token'] ?? null,
            'expires_in' => $json['expires_in'] ?? null,
        ];
    }

    public static function cacheKey(ManagedEnvironment $tenant, ?int $userId = null, ?string $sessionId = null): string
    {
        $suffix = $userId ? "user_{$userId}" : 'session_'.($sessionId ?: 'anon');

        return sprintf('rbac_delegated_token_%s_%s', $tenant->getKey(), $suffix);
    }

    private function sanitizeReturnPath(?string $path): ?string
    {
        if (empty($path)) {
            return null;
        }

        if (str_starts_with($path, 'http')) {
            $parsed = parse_url($path);
            $path = $parsed['path'] ?? '/';
        }

        return str_starts_with($path, '/') ? $path : '/';
    }
}
