<?php

namespace App\Http\Controllers\Portal;

use App\Domain\Identity\Services\AuthService;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;

/**
 * The only two endpoints the native shell needs, and deliberately the only two.
 *
 * The shell wraps the web portal, so it does not need an API — it needs a way
 * to hold a credential across app launches without keeping a cookie jar, and a
 * way to turn that credential into the ordinary web session everything else
 * already uses.
 *
 * The shell therefore holds **one** long-lived token, in the Keychain or
 * EncryptedSharedPreferences, and exchanges it for a fresh web session on
 * launch. It never holds a cookie-plus-token pair, the token never reaches
 * JavaScript, and `/app/*` is never exempted from CSRF "because the native app
 * cannot get a token" — that single shortcut is what turns a wrapper from safe
 * into trivially exploitable.
 *
 * The ability is `portal:session` and nothing else. The deleted API minted
 * tokens with `mobile:*`, which meant every endpoint it ever grew.
 */
class NativeSessionController extends Controller
{
    private const ABILITY = 'portal:session';

    private const TOKEN_NAME = 'portal-native';

    /**
     * Exchange credentials for a long-lived token, once, at first launch.
     *
     * Throttled by identifier and by IP, because the two abuses are different:
     * guessing one account's password, and spraying one password across many
     * accounts.
     */
    public function issueToken(Request $request, AuthService $auth): JsonResponse
    {
        $data = $request->validate([
            'identifier' => ['required', 'string', 'max:120'],
            'password' => ['required', 'string', 'max:200'],
            'device_name' => ['nullable', 'string', 'max:80'],
        ]);

        $keys = [
            'native-login:id:' . sha1(mb_strtolower($data['identifier'])),
            'native-login:ip:' . $request->ip(),
        ];

        foreach ($keys as $key) {
            if (RateLimiter::tooManyAttempts($key, 8)) {
                return response()->json([
                    'error' => __('محاولات كثيرة. حاول مرة أخرى بعد قليل.'),
                ], 429);
            }
        }

        $result = $auth->attempt($data['identifier'], $data['password'], $request->ip(), $request->userAgent());

        if (! $result->success) {
            foreach ($keys as $key) {
                RateLimiter::hit($key, 900);
            }

            // One message for every failure. Distinguishing "no such account"
            // from "wrong password" is an account-enumeration oracle.
            return response()->json(['error' => __('بيانات الدخول غير صحيحة')], 422);
        }

        $user = $result->user;

        if (! $user->can('portal.access')) {
            return response()->json(['error' => __('هذا الحساب ليس حساب مشترك')], 403);
        }

        foreach ($keys as $key) {
            RateLimiter::clear($key);
        }

        // One live native token per account: a new install retires the old one,
        // so a lost phone stops working as soon as the member signs in again.
        $user->tokens()->where('name', self::TOKEN_NAME)->delete();

        $token = $user->createToken(self::TOKEN_NAME, [self::ABILITY], now()->addYear());

        return response()->json([
            'token' => $token->plainTextToken,
            'expires_at' => $token->accessToken->expires_at?->toIso8601String(),
        ]);
    }

    /**
     * Turn the stored token into a web session and land on the portal.
     *
     * The shell calls this on launch, in the WebView, so the resulting cookie
     * belongs to the WebView's own jar and every subsequent request is an
     * ordinary session request with CSRF intact.
     */
    public function exchange(Request $request): RedirectResponse|JsonResponse
    {
        $user = $request->user();

        if (! $user) {
            return response()->json(['error' => 'unauthenticated'], 401);
        }

        if (! $user->can('portal.access')) {
            return response()->json(['error' => 'forbidden'], 403);
        }

        Auth::guard('web')->login($user, remember: true);
        $request->session()->regenerate();

        $user->forceFill(['portal_last_seen_at' => now()])->save();

        return redirect()->route('portal.home');
    }

    /**
     * Remote sign-out: drop the native token and the session together.
     */
    public function revoke(Request $request): JsonResponse
    {
        $user = $request->user();

        if ($user instanceof User) {
            $user->tokens()->where('name', self::TOKEN_NAME)->delete();
        }

        if ($request->hasSession()) {
            Auth::guard('web')->logout();
            $request->session()->invalidate();
        }

        return response()->json(['revoked' => true]);
    }
}
