<?php

namespace App\Http\Controllers;

use App\Models\EarnApiToken;
use App\Services\EarnApiAccessLogger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;

class EarnDeviceController extends Controller
{
    public function bind(Request $request, EarnApiAccessLogger $logs): JsonResponse
    {
        $token = $request->attributes->get('earn_api_token');
        abort_unless($token instanceof EarnApiToken, 401);
        $hash = $request->attributes->get('earn_device_hash');
        $preview = $request->attributes->get('earn_device_preview');
        $result = DB::transaction(function () use ($token, $hash, $preview, $request): array {
            $locked = EarnApiToken::query()->whereKey($token->id)->lockForUpdate()->firstOrFail();
            if ($locked->device_id_hash !== null) {
                $match = hash_equals($locked->device_id_hash, $hash);
                if ($match) {
                    $locked->forceFill(['last_ip' => $request->ip(), 'last_seen_at' => now()])->save();
                }

                return ['token' => $locked, 'match' => $match];
            }
            $locked->forceFill(['device_id_hash' => $hash, 'device_id_preview' => $preview, 'device_bound_at' => now(),
                'first_ip' => $request->ip(), 'last_ip' => $request->ip(), 'last_seen_at' => now()])->save();

            return ['token' => $locked, 'match' => true, 'created' => true];
        }, 3);
        if (! $result['match']) {
            $failedKey = 'earn-api:failed-device:'.hash('sha256', $result['token']->id.'|'.$request->ip());
            $failedLimit = (int) config('earn.api.failed_device_per_minute');
            if (RateLimiter::tooManyAttempts($failedKey, $failedLimit)) {
                $logs->record($request, 'rate_limited', 429, 'RATE_LIMITED', $result['token'], $hash, $preview);

                return $this->error('RATE_LIMITED', 429)->header('Retry-After', (string) RateLimiter::availableIn($failedKey));
            }
            RateLimiter::hit($failedKey, 60);
            $result['token']->forceFill(['device_mismatch_seen' => true])->saveQuietly();
            $logs->record($request, 'device_mismatch', 409, 'DEVICE_MISMATCH', $result['token'], $hash, $preview);

            return $this->error('DEVICE_MISMATCH', 409);
        }
        $already = ! ($result['created'] ?? false);
        $logs->record($request, $already ? 'device_bind_repeated' : 'device_bound', 200, null, $result['token'], $hash, $preview);

        return response()->json(['success' => true, 'data' => ['device_bound' => true, 'already_bound' => $already,
            'device_id_preview' => $result['token']->device_id_preview, 'bound_at' => $result['token']->device_bound_at?->toISOString()]])
            ->header('Cache-Control', 'no-store');
    }

    private function error(string $code, int $status): JsonResponse
    {
        return response()->json(['success' => false, 'error' => ['code' => $code, 'message' => __('earn_api.'.$code)]], $status)->header('Cache-Control', 'no-store');
    }
}
