<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Device;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class DeviceController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        $devices = Device::where('user_id', $request->user()->id)
            ->orderBy('name')
            ->get(['id', 'name', 'hostname', 'os_info', 'local_ip', 'online', 'last_seen_at', 'created_at']);

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

    public function claim(\App\Http\Requests\Api\ClaimDeviceRequest $request): JsonResponse
    {
        $normalized = \App\Support\PairingCode::normalize((string) $request->string('pairing_code'));
        if ($normalized === null) {
            return response()->json([
                'error' => ['code' => 'pairing_not_found', 'message' => 'Pairing code not found.'],
            ], 404);
        }

        $device = Device::where('pairing_code', $normalized)->first();
        if ($device === null) {
            return response()->json([
                'error' => ['code' => 'pairing_not_found', 'message' => 'Pairing code not found.'],
            ], 404);
        }

        if (!$device->isPairable()) {
            return response()->json([
                'error' => ['code' => 'pairing_expired', 'message' => 'Pairing code has expired.'],
            ], 410);
        }

        $device->update([
            'user_id' => $request->user()->id,
            'pairing_code' => null,
            'pairing_expires_at' => null,
        ]);

        return response()->json(['data' => $device->fresh()->only(
            ['id', 'name', 'hostname', 'os_info', 'local_ip', 'online', 'last_seen_at']
        )]);
    }

    public function update(\Illuminate\Http\Request $request, int $id): JsonResponse
    {
        $device = $this->ownedOrFail($request, $id);

        $validated = $request->validate([
            'name' => ['required', 'string', 'max:120'],
        ]);

        $device->update(['name' => $validated['name']]);
        return response()->json(['data' => $device->only(['id', 'name', 'hostname', 'os_info', 'local_ip', 'online', 'last_seen_at'])]);
    }

    public function destroy(\Illuminate\Http\Request $request, int $id): JsonResponse
    {
        $device = $this->ownedOrFail($request, $id);
        $device->delete();
        return response()->json(['ok' => true]);
    }

    public function snapshotFor(\App\Models\User $user): array
    {
        return Device::where('user_id', $user->id)
            ->get(['id', 'name', 'hostname', 'os_info', 'local_ip', 'online', 'last_seen_at'])
            ->keyBy('id')
            ->map(fn ($d) => $d->only(['id', 'name', 'hostname', 'os_info', 'local_ip', 'online', 'last_seen_at']))
            ->toArray();
    }

    public function diffSnapshots(array $previous, array $current): array
    {
        $changed = [];
        foreach ($current as $id => $row) {
            $prev = $previous[$id] ?? null;
            if ($prev === null || $prev !== $row) {
                $changed[] = $row;
            }
        }
        foreach ($previous as $id => $row) {
            if (!isset($current[$id])) {
                $changed[] = ['id' => $id, '_deleted' => true];
            }
        }
        return $changed;
    }

    public function stream(\Illuminate\Http\Request $request): \Symfony\Component\HttpFoundation\StreamedResponse
    {
        // The /devices/stream route lives outside auth:sanctum (so EventSource — which
        // can't set custom headers — can authenticate via ?token=). Accept either the
        // Bearer header OR the ?token= query string.
        if (!$request->user()) {
            $token = $request->bearerToken() ?: $request->query('token');
            if ($token) {
                $tokenModel = \Laravel\Sanctum\PersonalAccessToken::findToken($token);
                if ($tokenModel) {
                    $request->setUserResolver(fn () => $tokenModel->tokenable);
                }
            }
        }
        if (!$request->user()) {
            abort(401);
        }

        $user = $request->user();
        $maxTicks = (int) ($request->header('X-Test-Tick-Limit') ?? 0);
        $tickIntervalMs = 2000;
        $pingEverySeconds = 25;

        $streamed = new \App\Http\SseResponse(function () use ($user, $maxTicks, $tickIntervalMs, $pingEverySeconds) {
            @set_time_limit(0);
            ignore_user_abort(false);

            $previous = [];
            $tick = 0;
            $lastPing = time();

            $snapshot = $this->snapshotFor($user);
            $this->emitSse('device-update', ['devices' => array_values($snapshot)]);
            $previous = $snapshot;
            flush();

            while (true) {
                if ($maxTicks > 0 && $tick >= $maxTicks) break;
                if (connection_aborted()) break;

                $current = $this->snapshotFor($user);
                $diff = $this->diffSnapshots($previous, $current);
                if (!empty($diff)) {
                    $this->emitSse('device-update', ['changes' => $diff]);
                    $previous = $current;
                }

                if (time() - $lastPing >= $pingEverySeconds) {
                    $this->emitSse('ping', ['ts' => time()]);
                    $lastPing = time();
                }

                flush();
                if ($maxTicks > 0) { usleep(1000); } else { usleep($tickIntervalMs * 1000); }
                $tick++;
            }
        }, 200, [
            'Content-Type'    => 'text/event-stream',
            'Cache-Control'   => 'no-cache, no-store, must-revalidate',
            'X-Accel-Buffering' => 'no',
            'Connection'      => 'keep-alive',
        ]);

        return $streamed;
    }

    private function emitSse(string $event, array $data): void
    {
        echo "event: {$event}\n";
        echo 'data: ' . json_encode($data) . "\n\n";
    }

    private function ownedOrFail(\Illuminate\Http\Request $request, int $id): Device
    {
        $device = Device::where('id', $id)
            ->where('user_id', $request->user()->id)
            ->first();
        if ($device === null) {
            abort(404);
        }
        return $device;
    }
}
