<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\AdVisit;
use App\Models\Video;
use App\Models\VideoUnlock;
use App\Support\Viewer;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
 * Secure, backend-verified ad-unlock flow.
 *
 * The client can never mark a video unlocked directly. It must:
 *   1. Call start() -> receives an opaque, single-use token + an ad link.
 *   2. Open the ad link, stay >= required_seconds, then return.
 *   3. Call complete(token) -> backend validates the elapsed time server-side.
 * Only the server increments the valid-visit counter and grants the unlock.
 */
class UnlockController extends Controller
{
    /** Current unlock progress for the viewer. */
    public function status(Request $request, Video $video)
    {
        $viewer = Viewer::fromRequest($request);

        if (! $video->is_locked) {
            return response()->json([
                'locked' => false,
                'unlocked' => true,
            ]);
        }

        $unlocked = $video->isUnlockedFor($viewer->userId, $viewer->guestId);

        return response()->json([
            'locked' => true,
            'unlocked' => $unlocked,
            'required' => (int) $video->required_ad_visits,
            'completed' => $video->validVisitCountFor($viewer->userId, $viewer->guestId),
            'return_seconds' => (int) $video->return_seconds,
            'ad_links' => $video->ad_links ?? [],
        ]);
    }

    /** Issue a single-use visit token and hand back an ad link to open. */
    public function start(Request $request, Video $video)
    {
        $viewer = Viewer::fromRequest($request);

        if (! $video->is_locked) {
            return response()->json(['message' => 'Video is not locked.'], 422);
        }
        if (! $viewer->hasIdentity()) {
            return response()->json([
                'message' => 'A session identity is required. Send X-Guest-Id header or log in.',
            ], 422);
        }
        if ($video->isUnlockedFor($viewer->userId, $viewer->guestId)) {
            return response()->json(['message' => 'Already unlocked.', 'unlocked' => true]);
        }

        $links = $video->ad_links ?: [];
        if (empty($links)) {
            return response()->json(['message' => 'No ad link configured for this video.'], 422);
        }

        // Invalidate any stale, never-completed visits for this viewer/video so
        // only one visit can be "in flight" at a time (duplicate-click guard).
        $video->adVisits()
            ->whereNull('returned_at')
            ->where(fn ($q) => $this->scopeToViewer($q, $viewer))
            ->where('created_at', '<', Carbon::now()->subMinutes(15))
            ->delete();

        // Rotate the ad link based on how many visits are already valid.
        $index = $video->validVisitCountFor($viewer->userId, $viewer->guestId) % count($links);

        $visit = $video->adVisits()->create([
            'user_id' => $viewer->userId,
            'guest_id' => $viewer->guestId,
            'token' => Str::random(48),
            'required_seconds' => (int) $video->return_seconds,
            'clicked_at' => Carbon::now(),
            'ip' => $request->ip(),
        ]);

        return response()->json([
            'token' => $visit->token,
            'ad_link' => $links[$index],
            'required_seconds' => $visit->required_seconds,
        ]);
    }

    /** Verify the elapsed time and, if enough visits pass, grant the unlock. */
    public function complete(Request $request, Video $video)
    {
        $data = $request->validate([
            'token' => ['required', 'string', 'size:48'],
        ]);

        $viewer = Viewer::fromRequest($request);

        return DB::transaction(function () use ($video, $viewer, $data) {
            /** @var AdVisit|null $visit */
            $visit = AdVisit::where('token', $data['token'])
                ->where('video_id', $video->id)
                ->lockForUpdate()
                ->first();

            if (! $visit) {
                return response()->json(['message' => 'Invalid or expired token.'], 404);
            }

            // The visit must belong to the same viewer that started it.
            $sameViewer = ($viewer->userId && $visit->user_id === $viewer->userId)
                || ($viewer->guestId && $visit->guest_id === $viewer->guestId);
            if (! $sameViewer) {
                return response()->json(['message' => 'Token does not belong to this session.'], 403);
            }

            // Single-use: a token that already returned cannot be replayed.
            if ($visit->returned_at !== null) {
                return response()->json(['message' => 'This visit was already recorded.'], 409);
            }

            $visit->returned_at = Carbon::now();
            $elapsed = $visit->returned_at->diffInSeconds($visit->clicked_at, true);

            if ($elapsed < $visit->required_seconds) {
                $visit->valid = false;
                $visit->save();

                return response()->json([
                    'valid' => false,
                    'message' => 'You returned too early — please stay on the ad page for at least '
                        . $visit->required_seconds . ' seconds.',
                    'completed' => $video->validVisitCountFor($viewer->userId, $viewer->guestId),
                    'required' => (int) $video->required_ad_visits,
                ], 422);
            }

            $visit->valid = true;
            $visit->save();

            $completed = $video->validVisitCountFor($viewer->userId, $viewer->guestId);
            $required = (int) $video->required_ad_visits;
            $unlocked = false;

            if ($completed >= $required) {
                $expiry = (int) $video->unlock_expiry_hours;
                VideoUnlock::create([
                    'video_id' => $video->id,
                    'user_id' => $viewer->userId,
                    'guest_id' => $viewer->guestId,
                    'unlocked_at' => Carbon::now(),
                    'expires_at' => $expiry > 0 ? Carbon::now()->addHours($expiry) : null,
                ]);
                $unlocked = true;
            }

            return response()->json([
                'valid' => true,
                'completed' => $completed,
                'required' => $required,
                'unlocked' => $unlocked,
                'message' => $unlocked
                    ? 'Unlocked! Enjoy the video.'
                    : 'Visit counted. ' . $completed . ' / ' . $required . ' completed.',
            ]);
        });
    }

    private function scopeToViewer($query, Viewer $viewer)
    {
        if ($viewer->userId) {
            return $query->where('user_id', $viewer->userId);
        }
        return $query->where('guest_id', $viewer->guestId);
    }
}
