<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api\Pwa;

use App\Http\Controllers\Controller;
use App\Http\Requests\Pwa\LoginRequest;
use App\Http\Requests\Pwa\RegisterDeviceRequest;
use App\Models\User;
use App\Support\PwaAuthResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    /**
     * Roles permitidos para acceso a la PWA.
     */
    private const ALLOWED_ROLES = ['collector', 'supervisor', 'admin'];

    /**
     * POST /api/pwa/auth/login
     *
     * Autentica un usuario y retorna token de dispositivo.
     * Acepta email o teléfono como identificador.
     * Acepta roles: collector, supervisor, admin.
     */
    public function login(LoginRequest $request): JsonResponse
    {
        $searchField = $request->getSearchField();
        $identifier = $request->getNormalizedIdentifier();

        // Buscar por email (exacto) o teléfono (normalizado ambos lados).
        //
        // Para teléfonos usamos REGEXP_REPLACE en la columna para eliminar caracteres
        // no numéricos del valor almacenado (+57, espacios, guiones, etc.), luego un
        // sufijo-LIKE contra el identificador ya normalizado.
        //
        // El sufijo-LIKE maneja el código de país: "+57 3001234567" normaliza a
        // "573001234567", que termina en "3001234567" (lo que el usuario escribe).
        //
        // Requisito mínimo de 7 dígitos: elimina el vector de sufijos cortos. Un
        // sufijo de 4 dígitos podría colisionar con múltiples usuarios; con 7+ dígitos
        // la probabilidad de colisión es despreciable en cualquier base de datos.
        // El identifier llega ya normalizado (solo dígitos) desde LoginRequest.
        if ($searchField === 'phone') {
            if (strlen($identifier) < 7) {
                $user = null;
            } else {
                // El sufijo-LIKE puede colisionar entre usuarios (incluso de
                // empresas distintas). En lugar de elegir arbitrariamente el
                // primero con ->first() —lo que podría impedir el login legítimo
                // de otro usuario con el mismo sufijo— recuperamos todos los
                // candidatos y autenticamos contra aquel cuya contraseña coincide.
                $user = User::whereRaw(
                    "REGEXP_REPLACE(phone, '[^0-9]', '') LIKE ?",
                    ['%'.$identifier]
                )->get()->first(
                    fn (User $candidate): bool => Hash::check($request->password, $candidate->password)
                );
            }
        } else {
            $user = User::where('email', $identifier)->first();
        }

        if (! $user || ! Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'identifier' => ['Credenciales incorrectas.'],
            ]);
        }

        // Verificar rol usando Spatie hasAnyRole()
        if (! $user->hasAnyRole(self::ALLOWED_ROLES)) {
            throw ValidationException::withMessages([
                'identifier' => ['Solo usuarios autorizados pueden acceder a la app.'],
            ]);
        }

        // Verificar que tiene company asignada (company-scoped)
        if (! $user->company_id) {
            throw ValidationException::withMessages([
                'identifier' => ['Usuario sin empresa asignada.'],
            ]);
        }

        // Verificar suscripcion activa de la company
        if (! $user->hasActiveSubscription()) {
            throw ValidationException::withMessages([
                'identifier' => ['La suscripcion de tu empresa ha expirado.'],
            ]);
        }

        // Obtener rol PWA del usuario usando Spatie
        $userRole = $this->getPwaRole($user);

        // Crear token con nombre del dispositivo y expiración absoluta de 30 días.
        // La expiración absoluta previene que tokens usados con baja frecuencia vivan
        // indefinidamente mediante la expiración por inactividad de Sanctum.
        // El nombre del token identifica el DISPOSITIVO, y por eso usa el
        // `device_id` que la PWA genera una vez y persiste.
        //
        // Antes era `device_name` = `PWA-<userAgent recortado a 50>`. Dos iPhone
        // con la misma version de iOS producian EXACTAMENTE la misma cadena: en
        // produccion el cobrador y el dueno compartian nombre de dispositivo. Con
        // la revocacion "un token por dispositivo", entrar desde el segundo equipo
        // cerraba la sesion del primero — y ese cierre involuntario borraba la
        // cola de pagos sin sincronizar.
        //
        // `registerDevice()` ya renombraba el token al `device_id`; el login se
        // habia quedado atras. Ahora los dos usan el mismo criterio.
        $deviceId = trim((string) $request->input('device_id', ''));
        $tokenName = $deviceId !== ''
            ? $deviceId
            : ($request->device_name ?? 'pwa-'.now()->timestamp);

        // Un token vivo por dispositivo. Sin esto cada inicio dejaba vivo el token
        // anterior durante sus 30 dias: un solo cobrador acumulo 495 credenciales
        // validas, y al perder un telefono no habia forma de saber cual revocar.
        $user->tokens()->where('name', $tokenName)->delete();

        // Limpieza del esquema viejo: los tokens nombrados con el userAgent son
        // ambiguos entre dispositivos, asi que no se pueden revocar con precision.
        // Se descartan en el primer login de cada usuario tras este cambio; despues
        // no queda ninguno y esto es un no-op.
        $user->tokens()->where('name', 'like', 'PWA-Mozilla%')->delete();

        $token = $user->createToken($tokenName, ["pwa:{$userRole}"], now()->addDays(30))->plainTextToken;

        return response()->json([
            'token' => $token,
            'user' => $this->formatUserResponse($user, $userRole),
            // Con qué nombre quedó registrado este dispositivo (su `device_id`).
            'device_name' => $tokenName,
        ], 200);
    }

    /**
     * Obtiene el rol PWA del usuario (delegado al helper compartido con el
     * acceso demo, para no duplicar la lógica de prioridad de roles).
     */
    private function getPwaRole(User $user): string
    {
        return PwaAuthResponse::pwaRole($user);
    }

    /**
     * Formatea la respuesta del usuario con rol y permisos (shape canónico
     * compartido con el acceso demo).
     */
    private function formatUserResponse(User $user, string $role): array
    {
        return PwaAuthResponse::userPayload($user, $role);
    }

    /**
     * POST /api/pwa/auth/logout
     *
     * Revoca el token actual.
     */
    public function logout(Request $request): JsonResponse
    {
        // Revocar token actual
        $request->user()->currentAccessToken()->delete();

        return response()->json([
            'message' => 'Sesión cerrada correctamente.',
        ]);
    }

    /**
     * GET /api/pwa/auth/me
     *
     * Retorna datos del usuario autenticado con rol dinamico.
     */
    public function me(Request $request): JsonResponse
    {
        $user = $request->user();
        $role = $this->getPwaRole($user);

        return response()->json($this->formatUserResponse($user, $role));
    }

    /**
     * POST /api/pwa/auth/register-device
     *
     * Registra un device_id para el usuario.
     */
    public function registerDevice(RegisterDeviceRequest $request): JsonResponse
    {
        $user = $request->user();

        // Guardar device_id en el token actual
        $token = $user->currentAccessToken();
        $token->name = $request->device_id;
        $token->save();

        return response()->json([
            'message' => 'Dispositivo registrado.',
            'device_id' => $request->device_id,
        ]);
    }

    /**
     * DELETE /api/pwa/auth/devices/{tokenId}
     *
     * Revoca un token específico (cerrar sesión en otro dispositivo).
     */
    public function revokeDevice(Request $request, int $tokenId): JsonResponse
    {
        $user = $request->user();

        $token = $user->tokens()->find($tokenId);

        if (! $token) {
            return response()->json([
                'message' => 'Dispositivo no encontrado.',
            ], 404);
        }

        $token->delete();

        return response()->json([
            'message' => 'Dispositivo desvinculado.',
        ]);
    }

    /**
     * GET /api/pwa/auth/devices
     *
     * Lista todos los dispositivos/tokens del usuario.
     */
    public function devices(Request $request): JsonResponse
    {
        $user = $request->user();
        $currentTokenId = $user->currentAccessToken()->id;

        $devices = $user->tokens()->get()->map(fn ($token) => [
            'id' => $token->id,
            'name' => $token->name,
            'last_used_at' => $token->last_used_at?->toIso8601String(),
            'created_at' => $token->created_at->toIso8601String(),
            'is_current' => $token->id === $currentTokenId,
        ]);

        return response()->json([
            'devices' => $devices,
        ]);
    }
}
