<?php

namespace App\Http\Controllers;

use App\Livewire\ConsentScreen;
use App\Models\Application;
use App\Models\AuthenticationToken;
use App\Models\User;
use DateTimeImmutable;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\JwtFacade;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Illuminate\Http\Request;

class OIDCController extends Controller
{
    public function authorize(Request $request)
    {
        $client = Application::where('client_id', $request->client_id)->firstOrFail();

        if ($client->redirect_uri !== $request->redirect_uri) {
            abort(403, 'Redirect URI mismatch');
        }

        $user = auth()->user();
        
        // Check if user has auto-approval enabled and has previously authorized this app
        $hasAuthorizedBefore = $user->tokens()->where('application_id', $client->id)->exists();
        
        if ($user->auto_approve_apps && $hasAuthorizedBefore) {
            // Auto-approve: generate code and redirect directly
            $code = Str::random(40);
            Log::info("Auto-approving and caching code: $code");
            Cache::put("auth_code:$code", [
                'user_id' => $user->id,
                'client_id' => $client->id,
                'scope' => $request->scope,
                'code_challenge' => $request->code_challenge ?? null,
                'code_challenge_method' => $request->code_challenge_method ?? null,
                'nonce' => $request->input('nonce') ?? null,
            ], now()->addMinutes(5));

            return redirect($request->redirect_uri . '?code=' . $code . '&state=' . $request->state);
        }

        // Standard flow: show confirmation screen
        $code = Str::random(40);
        Log::info("Caching code: $code");
        Cache::put("auth_code:$code", [
            'user_id' => $user->id,
            'client_id' => $client->id,
            'scope' => $request->scope,
            'code_challenge' => $request->code_challenge ?? null,
            'code_challenge_method' => $request->code_challenge_method ?? null,
            'nonce' => $request->input('nonce') ?? null,
        ], now()->addMinutes(5));

        session([
            'app_id' => $client->id,
            'redirect_on_confirm' => $request->redirect_uri . '?code=' . $code . '&state=' . $request->state
        ]);

        return redirect(route('auth.confirm'));
    }


    public function token(Request $request)
    {
        Log::info("Got back code: {$request->code}");
        $payload = Cache::pull("auth_code:{$request->code}");
        if (!$payload) {
            Log::error('Invalid or expired auth code');
            abort(403, 'Invalid or expired auth code');
        }

        Log::info($payload);

        // We only trust the client ID we saved from the /authorize request. Fuck
        // whatever comes in the request
        $client = Application::findOrFail($payload['client_id']);

        if ($request->has('code_verifier')) {
            // PKCE validation
            $verifier = $request->code_verifier;
            $method = $payload['code_challenge_method'] ?? 'plain';

            $valid = match ($method) {
                'S256' => rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=') === $payload['code_challenge'],
                'plain' => $verifier === $payload['code_challenge'],
                default => false,
            };

            if (!$valid) {
                abort(403, 'Invalid PKCE code_verifier');
            }
        } elseif ($request->has('client_id') && $request->has('client_secret')) {
            // Client credentials validation
            if ($request->client_id !== $client->client_id) {
                abort(403, 'Client ID mismatch');
            }

            if (!hash_equals($client->client_secret, $request->client_secret)) {
                abort(403, 'Invalid client secret');
            }
        } else {
            // No authentication provided
            abort(403, 'Missing authentication');
        }

        // Validate redirect_uri
        if ($request->redirect_uri !== $client->redirect_uri) {
            abort(403, 'Redirect URI mismatch');
        }

        // if ($client->redirect_uri !== $request->redirect_uri) {
        //     Log::error('Redirect URI mismatch');
        //     abort(403, 'Redirect URI mismatch');
        // }


        $user = User::find($payload['user_id']);

        // Generate ID token (JWT)
        Log::info("GENERATING TOKEN");
        $privateKey = InMemory::file($this->getPrivateKeyPath());

        $token = (new JwtFacade())->issue(
            new Sha256(),
            $privateKey,
            function (Builder $builder, DateTimeImmutable $issuedAt) use ($client, $user, $payload) {
                $builder = $builder
                    ->issuedBy(config('app.url'))
                    ->permittedFor($client->client_id)
                    ->relatedTo((string) $user->uuid)
                    ->issuedAt($issuedAt)
                    ->expiresAt($issuedAt->modify('+5 minutes'))
                    ->withClaim('email', $user->email);

                if (!empty($payload['nonce'])) {
                    $builder = $builder->withClaim('nonce', $payload['nonce']);
                }

                return $builder;
            }
        )->toString();

        $accessToken = Str::random(64);

        $user->tokens()->create([
            'application_id' => $client->id,
            'token' => $accessToken,
            'issued_at' => now()->toDateTimeString(),
            'expires_at' => now()->addMonth()->toDateTimeString(),
            'ip' => $request->ip(),
            'user_agent' => $request->userAgent(),
        ]);

        return response()->json([
            'access_token' => $accessToken,
            'token_type' => 'Bearer',
            'expires_in' => 3600,
            'id_token' => $token,
        ]);
    }


    public function userinfo(Request $request)
    {
        $authHeader = $request->header('Authorization');

        if (!$authHeader || !Str::startsWith($authHeader, 'Bearer ')) {
            return response()->json(['error' => 'invalid_request'], 400);
        }

        $accessToken = Str::after($authHeader, 'Bearer ');

        $token = AuthenticationToken::where('token', $accessToken)->first();
        // TODO: Set token expirations
        // if (!$token || $token->expires_at->isPast()) {
        //     return response()->json(['error' => 'invalid_token'], 401);
        // }
        
        if (empty($token)) {
            return response()->json(['error' => 'invalid_token'], 401);
        }
        
        $user = $token->user;
        if (empty($user)) {
            return response()->json(['error' => 'invalid_token'], 401);
        }


        return response()->json([
            'sub' => (string) $user->uuid,
            'email' => $user->email,
            'name' => $user->name,
            'preferred_username' => $user->preferred_username,
            'picture' => $user->avatar ? $user->avatarUrl() : null
        ]);
    }

    public function jwks()
    {
        $pubKeyPath = $this->getPublicKeyPath();
        $keyDetails = openssl_pkey_get_details(openssl_pkey_get_public(file_get_contents($pubKeyPath)));

        $modulus = $keyDetails['rsa']['n'];
        $exponent = $keyDetails['rsa']['e'];

        return response()->json([
            'keys' => [[
                'kty' => 'RSA',
                'use' => 'sig',
                'alg' => 'RS256',
                'kid' => 'main-key', // optional, but good for key rotation
                'n'   => rtrim(strtr(base64_encode($modulus), '+/', '-_'), '='),
                'e'   => rtrim(strtr(base64_encode($exponent), '+/', '-_'), '='),
            ]]
        ]);
    }

    public function openidConfig()
    {
        return response()->json([
            'issuer' => config('app.url'),
            'authorization_endpoint' => route('auth.authorize'),
            'token_endpoint' => route('auth.token'),
            'userinfo_endpoint' => route('auth.userinfo'),
            'scopes_supported' => ["openid", "profile", "email"],
            'response_types_supported' => ["code"],
            "jwks_uri" => route('auth.keys'),
            "id_token_signing_alg_values_supported" => ["RS256"],
            'claims_supported' => [
                'sub',
                'email',
                'name',
                'preferred_username',
                'picture'
            ]
        ]);
    }

    public function logout(Request $request)
    {
        return view('logged-out');
    }

    /**
     * Get the private key path based on environment.
     */
    protected function getPrivateKeyPath(): string
    {
        if (app()->environment('testing')) {
            return storage_path('testing/oauth/private.pem');
        }

        return storage_path('oauth/private.pem');
    }

    /**
     * Get the public key path based on environment.
     */
    protected function getPublicKeyPath(): string
    {
        if (app()->environment('testing')) {
            return storage_path('testing/oauth/public.pem');
        }

        return storage_path('oauth/public.pem');
    }
}
