<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Models\Wallet;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpKernel\Exception\LockedHttpException;

class AuthController extends Controller
{
    public function register(Request $request): JsonResponse
    {
        $request->merge(['email' => Str::lower((string) $request->input('email'))]);

        $data = $request->validate([
            'email' => ['required', 'email:rfc', 'max:255', Rule::unique('users', 'email')],
            'password' => ['required', 'string', 'min:8', 'max:72'],
            'firstName' => ['nullable', 'string', 'max:255'],
            'lastName' => ['nullable', 'string', 'max:255'],
            'phone' => ['nullable', 'string', 'max:30'],
            'preferredCurrency' => ['nullable', Rule::in(['USD', 'EUR', 'GBP', 'NGN'])],
            'device_name' => ['nullable', 'string', 'max:255'],
        ]);

        $user = DB::transaction(function () use ($data): User {
            $user = User::query()->create([
                'email' => Str::lower($data['email']),
                'password_hash' => Hash::make($data['password']),
                'first_name' => $data['firstName'] ?? null,
                'last_name' => $data['lastName'] ?? null,
                'phone' => $data['phone'] ?? null,
                'preferred_currency' => $data['preferredCurrency'] ?? 'USD',
                'role' => 'USER',
                'status' => 'PENDING_VERIFICATION',
            ]);

            Wallet::query()->create(['user_id' => $user->id]);

            return $user;
        });

        return response()->json($this->tokenResponse($user, $data['device_name'] ?? null), 201);
    }

    /** @throws AuthenticationException */
    public function login(Request $request): JsonResponse
    {
        $request->merge(['email' => Str::lower((string) $request->input('email'))]);

        $data = $request->validate([
            'email' => ['required', 'email:rfc', 'max:255'],
            'password' => ['required', 'string', 'max:72'],
            'device_name' => ['nullable', 'string', 'max:255'],
        ]);

        $result = DB::transaction(function () use ($data, $request): array {
            $user = User::query()
                ->where('email', Str::lower($data['email']))
                ->lockForUpdate()
                ->first();

            if (! $user) {
                return ['error' => 'invalid'];
            }

            if ($user->locked_until?->isFuture()) {
                return ['error' => 'locked'];
            }

            if (! Hash::check($data['password'], $user->password_hash)) {
                $attempts = $user->failed_login_count + 1;
                $lockAttempts = (int) config('auth.lock_attempts', 5);
                $user->forceFill([
                    'failed_login_count' => $attempts,
                    'locked_until' => $attempts >= $lockAttempts
                        ? now()->addMinutes((int) config('auth.lock_minutes', 15))
                        : null,
                ])->save();

                return ['error' => $attempts >= $lockAttempts ? 'locked' : 'invalid'];
            }

            if (in_array($user->status, ['SUSPENDED', 'DELETED'], true)) {
                return ['error' => 'disabled'];
            }

            $user->forceFill([
                'last_login_at' => now(),
                'last_login_ip' => $request->ip(),
                'failed_login_count' => 0,
                'locked_until' => null,
            ])->save();

            return ['user' => $user];
        });

        if (($result['error'] ?? null) === 'locked') {
            throw new LockedHttpException('Account is temporarily locked.');
        }
        if (($result['error'] ?? null) === 'disabled') {
            throw new AuthenticationException('Account is suspended.');
        }
        if (isset($result['error'])) {
            throw new AuthenticationException('Invalid credentials.');
        }

        return response()->json($this->tokenResponse($result['user'], $data['device_name'] ?? null));
    }

    public function logout(Request $request): JsonResponse
    {
        $request->user()->currentAccessToken()?->delete();

        return response()->json(['message' => 'Logged out successfully.']);
    }

    public function user(Request $request): JsonResponse
    {
        return response()->json(['user' => $request->user()]);
    }

    private function tokenResponse(User $user, ?string $deviceName): array
    {
        $expiresAt = now()->addMinutes((int) config('sanctum.expiration', 10080));
        $token = $user->createToken($deviceName ?: 'api', ['*'], $expiresAt)->plainTextToken;

        return [
            'user' => $user->fresh(),
            'accessToken' => $token,
            'tokenType' => 'Bearer',
            'expiresAt' => $expiresAt->toIso8601String(),
        ];
    }
}