<?php

namespace App\Http\Controllers;

use App\Jobs\EnrichQrCodeScan;
use App\Models\QrCode;
use App\Models\QrCodeScan;
use App\Services\QrCode\IpAnonymizer;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpFoundation\Response;

/**
 * Public, unauthenticated `/q/{short_code}` redirect handler.
 *
 * This is the entire reason the scan analytics exist: every scan of a
 * printed QR code hits this controller before it hits the real target.
 * Keep it fast — no geo HTTP call, no device parsing inline. That work is
 * deferred to {@see EnrichQrCodeScan}, a queued job.
 */
class QrRedirectController extends Controller
{
    public function __invoke(Request $request, QrCode $qrCode): RedirectResponse
    {
        if (! $qrCode->is_active) {
            abort(Response::HTTP_GONE, 'This QR code has been deactivated.');
        }

        if (! $qrCode->is_dynamic || $qrCode->target_url === null) {
            abort(Response::HTTP_NOT_FOUND);
        }

        $fullIp = (string) $request->ip();
        $userAgent = (string) $request->userAgent();
        $scannedAt = Carbon::now();

        $visitorHash = IpAnonymizer::visitorHash(
            $fullIp,
            $userAgent,
            $scannedAt,
            (string) config('qr.visitor_hash_salt'),
        );

        $storedIp = config('qr.store_full_ip') ? $fullIp : IpAnonymizer::truncate($fullIp);

        $scan = QrCodeScan::query()->create([
            'qr_code_id' => $qrCode->id,
            'ip_address' => $storedIp,
            'visitor_hash' => $visitorHash,
            'user_agent' => $userAgent,
            'referer' => $request->headers->get('referer'),
            'is_bot' => false,
            'scanned_at' => $scannedAt,
        ]);

        // Single atomic query: increments scan_count and sets
        // last_scanned_at together, avoiding a lost-update race under
        // concurrent scans.
        $qrCode->increment('scan_count', 1, ['last_scanned_at' => $scannedAt]);

        EnrichQrCodeScan::dispatch($scan->id);

        // 302 (temporary), not 301: a 301 gets cached by the browser
        // forever, so every subsequent scan from that device skips this
        // server entirely — permanently destroying analytics for that
        // visitor and making the destination un-updatable for them even
        // if the admin later edits the target URL. The Cache-Control
        // header below reinforces the same intent at the HTTP layer.
        return redirect()->away($qrCode->target_url, Response::HTTP_FOUND)
            ->header('Cache-Control', 'no-store, no-cache, must-revalidate');
    }
}
