<?php

namespace App\Http\Controllers;

use App\Services\TwitchHelix;
use App\Support\RigData;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Throwable;

/**
 * Web-based equivalent of `php artisan rig:loading` — guides the streamer
 * through the Project Loading manifest fields and pushes the channel
 * update to Twitch via Helix.
 *
 * Lives at /onboard so it's reachable from the same server as the
 * overlays (bookmark http://127.0.0.1:1118/onboard).
 */
class OnboardController extends Controller
{
    public function show(RigData $rig, TwitchHelix $twitch)
    {
        return view('onboard', [
            'manifest'      => $rig->loading(),
            'telemetry'     => $rig->telemetry(),
            'trackCount'    => $rig->playlistCount(),
            'twitchEnabled' => $twitch->enabled(),
        ]);
    }

    public function search(Request $request, TwitchHelix $twitch): JsonResponse
    {
        if (!$twitch->enabled()) {
            return response()->json(['error' => 'twitch not configured'], 503);
        }
        $q = trim((string) $request->query('q', ''));
        if (mb_strlen($q) < 2) {
            return response()->json(['matches' => []]);
        }
        try {
            // Use the HTTP API directly to get the top-N candidates, not just
            // the single best-match the Artisan command uses.
            $hits = $this->rawSearch($twitch, $q);
        } catch (Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 502);
        }
        return response()->json(['matches' => $hits]);
    }

    public function submit(Request $request, RigData $rig, TwitchHelix $twitch): JsonResponse
    {
        $data = $request->validate([
            'game'         => 'required|string|max:200',
            'gameId'       => 'nullable|string|max:32',
            'subtitle'     => 'nullable|string|max:300',
            'countdownMin' => 'required|integer|min:0|max:120',
            'camera'       => 'required|boolean',
            'microphone'   => 'required|boolean',
            'pushTwitch'   => 'nullable|boolean',
        ]);

        $manifest = [
            'compiledAt'   => gmdate('Y-m-d\TH:i:s\Z'),
            'game'         => $data['game'],
            'gameId'       => $data['gameId']   ?? null,
            'subtitle'     => $data['subtitle'] ?: null,
            'countdownMin' => (int) $data['countdownMin'],
            'camera'       => (bool) $data['camera'],
            'microphone'   => (bool) $data['microphone'],
        ];

        $path = rtrim(config('rig.storage_data'), '/') . '/loading.json';
        @mkdir(dirname($path), 0775, true);
        file_put_contents(
            $path,
            json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"
        );

        $twitchStatus = null;
        if (!empty($data['pushTwitch']) && $twitch->enabled() && !empty($manifest['gameId'])) {
            try {
                $title = $manifest['subtitle'] ?: $manifest['game'];
                $twitch->setChannel($title, $manifest['gameId']);
                $twitchStatus = "ok: \"{$title}\" / {$manifest['game']}";
            } catch (Throwable $e) {
                $twitchStatus = 'failed: ' . $e->getMessage();
            }
        }

        return response()->json([
            'ok'       => true,
            'manifest' => $manifest,
            'twitch'   => $twitchStatus,
            'path'     => str_replace(base_path() . '/', '', $path),
        ]);
    }

    /**
     * Raw search/categories Helix call — same shape as TwitchHelix internally
     * but returns multiple candidates instead of the first exact match.
     */
    private function rawSearch(TwitchHelix $twitch, string $q): array
    {
        $reflection = new \ReflectionClass($twitch);
        $http = $reflection->getProperty('http')->getValue($twitch);
        $headers = $reflection->getMethod('headers');
        $headers->setAccessible(true);

        $res = $http->get('search/categories', [
            'headers' => $headers->invoke($twitch),
            'query'   => ['query' => $q, 'first' => 10],
        ]);
        $body = json_decode((string) $res->getBody(), true);
        $hits = $body['data'] ?? [];

        // Sort so case-insensitive exact matches surface first.
        usort($hits, function ($a, $b) use ($q) {
            $ax = strcasecmp((string) ($a['name'] ?? ''), $q) === 0 ? 0 : 1;
            $bx = strcasecmp((string) ($b['name'] ?? ''), $q) === 0 ? 0 : 1;
            return $ax <=> $bx;
        });

        return array_map(fn($r) => [
            'id'      => (string) ($r['id'] ?? ''),
            'name'    => (string) ($r['name'] ?? ''),
            'boxArt'  => (string) ($r['box_art_url'] ?? ''),
        ], array_slice($hits, 0, 10));
    }
}
