<?php

namespace App\Http\Middleware;

use App\Models\ApiKey;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Hybrid auth: accept either an API-key Bearer token OR an existing Sanctum
 * cookie session. Lets the same `/api/*` route serve the SPA frontend AND
 * external integrations (AI Portal, custom scripts) without duplicating
 * routes.
 *
 * Decision tree:
 *   1. Authorization: Bearer <token> present → validate as API key.
 *   2. Sanctum cookie present + valid → already authenticated by the
 *      EnsureFrontendRequestsAreStateful middleware higher up; allow through.
 *   3. Neither → 401.
 *
 * Ability gate (e.g. `api.auth:read`) only applies to API-key auth — Sanctum
 * users use role-based checks (canManageServers etc.) which the controllers
 * already enforce.
 */
class ApiKeyOrSanctum
{
    public function handle(Request $request, Closure $next, ?string $ability = null): Response
    {
        $header = $request->header('Authorization', '');
        $hasBearer = is_string($header) && str_starts_with($header, 'Bearer ');
        $bearerToken = $hasBearer ? trim(substr($header, 7)) : ($request->query('api_token') ?: null);

        // Path 1: API-key bearer token.
        if ($bearerToken) {
            $hash = hash('sha256', $bearerToken);
            $key = ApiKey::with('user')->where('token_hash', $hash)->first();

            if (!$key) {
                return response()->json(['error' => 'invalid_token'], 401);
            }
            if ($key->revoked) {
                return response()->json(['error' => 'token_revoked'], 401);
            }
            if ($key->expires_at && $key->expires_at->isPast()) {
                return response()->json(['error' => 'token_expired', 'expired_at' => $key->expires_at->toIso8601String()], 401);
            }
            if (!$key->user) {
                return response()->json(['error' => 'orphaned_token'], 401);
            }
            if ($ability) {
                $abilities = $key->abilities ?? [];
                if (!in_array($ability, $abilities, true) && !in_array('*', $abilities, true)) {
                    return response()->json([
                        'error' => 'insufficient_scope',
                        'message' => "Token missing '{$ability}' ability.",
                    ], 403);
                }
            }
            // Throttle the last_used_at write so a chatty client doesn't
            // hammer the api_keys row on every request.
            if (!$key->last_used_at || $key->last_used_at->lt(now()->subMinute())) {
                $key->forceFill(['last_used_at' => now()])->save();
            }
            auth()->setUser($key->user);
            $request->setUserResolver(fn () => $key->user);
            $request->attributes->set('api_key', $key);

            return $next($request);
        }

        // Path 2: Sanctum cookie. EnsureFrontendRequestsAreStateful runs
        // earlier in the pipeline — by the time we get here, auth()->user()
        // is populated for valid cookie sessions.
        if ($request->user()) {
            return $next($request);
        }

        return response()->json(['error' => 'unauthorized', 'message' => 'Bearer token or session required.'], 401);
    }
}
