<?php

namespace App\Services;

/**
 * Captures the static device + OBS config snapshot shown on the landing
 * overlay's telemetry rotator. Direct port of scripts/telemetry.sh.
 */
class HardwareSnapshot
{
    public function collect(): array
    {
        return [
            'collectedAt' => gmdate('Y-m-d\TH:i:s\Z'),
            'rig'         => $this->rigName(),
            'cpu'         => $this->cpu(),
            'mem'         => $this->mem(),
            'host'        => ['kernel' => $this->kernel()],
            'gpu'         => $this->gpu(),
            'obs'         => $this->obs(),
        ];
    }

    private function rigName(): string
    {
        $name = (string) (config('rig.rig_name') ?: gethostname());
        return ucfirst(strtolower($name));
    }

    private function cpu(): array
    {
        $model = null;
        if (is_readable('/proc/cpuinfo')) {
            foreach (file('/proc/cpuinfo') as $line) {
                if (preg_match('/^model name\s*:\s*(.+)$/', $line, $m)) {
                    $model = trim($m[1]);
                    break;
                }
            }
        }
        $threads = (int) (shell_exec('nproc 2>/dev/null') ?: 0);
        return ['model' => $model, 'threads' => $threads ?: null];
    }

    private function mem(): array
    {
        $kb = 0;
        if (is_readable('/proc/meminfo')) {
            foreach (file('/proc/meminfo') as $line) {
                if (preg_match('/^MemTotal:\s+(\d+)\s*kB/', $line, $m)) {
                    $kb = (int) $m[1];
                    break;
                }
            }
        }
        $gb = $kb > 0 ? round($kb / 1024 / 1024, 1) : null;
        return ['totalGB' => $gb];
    }

    private function kernel(): ?string
    {
        $k = trim((string) shell_exec('uname -r 2>/dev/null'));
        return $k === '' ? null : $k;
    }

    private function gpu(): array
    {
        $name = null;
        $vram = null;
        if (trim((string) shell_exec('command -v nvidia-smi 2>/dev/null')) !== '') {
            $row = trim((string) shell_exec(
                'nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits 2>/dev/null | head -n1'
            ));
            if ($row !== '') {
                [$n, $v] = array_pad(array_map('trim', explode(',', $row, 2)), 2, null);
                $name = $n !== '' ? $n : null;
                $vram = is_numeric($v) ? (int) $v : null;
            }
        }
        return ['name' => $name, 'vramTotalMB' => $vram];
    }

    private function obs(): ?array
    {
        $obsDir = rtrim((string) config('rig.obs_studio_dir'), '/');
        $userIni = $obsDir . '/user.ini';
        if (!is_file($userIni)) return null;
        $user = $this->parseIni($userIni);

        $profile = $user['Basic']['Profile'] ?? 'Untitled';
        $renderer = $user['Video']['Renderer'] ?? null;

        $profileDir = "{$obsDir}/basic/profiles/{$profile}";
        $basicIni = "{$profileDir}/basic.ini";
        if (!is_file($basicIni)) return null;
        $b = $this->parseIni($basicIni);

        $mode  = $b['Output']['Mode']  ?? null;
        $baseW = isset($b['Video']['BaseCX'])   ? (int) $b['Video']['BaseCX']   : null;
        $baseH = isset($b['Video']['BaseCY'])   ? (int) $b['Video']['BaseCY']   : null;
        $outW  = isset($b['Video']['OutputCX']) ? (int) $b['Video']['OutputCX'] : null;
        $outH  = isset($b['Video']['OutputCY']) ? (int) $b['Video']['OutputCY'] : null;
        $fps   = isset($b['Video']['FPSInt'])   ? (int) $b['Video']['FPSInt']   : null;

        $encRaw = '';
        $bitrate = null;
        $rc = null;
        $keyint = null;
        $h264Profile = null;

        if ($mode === 'Advanced') {
            $encRaw = $b['AdvOut']['Encoder'] ?? '';
            $sej = "{$profileDir}/streamEncoder.json";
            if (is_file($sej)) {
                $j = json_decode((string) file_get_contents($sej), true) ?: [];
                $rc          = $j['rate_control'] ?? null;
                $bitrate     = isset($j['bitrate']) ? (int) $j['bitrate'] : null;
                $keyint      = isset($j['keyint_sec']) ? (int) $j['keyint_sec'] : null;
                $h264Profile = $j['profile'] ?? null;
            }
        } else {
            $encRaw  = $b['SimpleOutput']['StreamEncoder'] ?? '';
            $bitrate = isset($b['SimpleOutput']['VBitrate']) ? (int) $b['SimpleOutput']['VBitrate'] : null;
        }

        return [
            'profile'    => $profile,
            'renderer'   => $renderer,
            'outputMode' => $mode,
            'canvas'     => ['w' => $baseW, 'h' => $baseH],
            'output'     => ['w' => $outW, 'h' => $outH],
            'fps'        => $fps,
            'color'      => [
                'format' => $b['Video']['ColorFormat'] ?? null,
                'space'  => $b['Video']['ColorSpace']  ?? null,
                'range'  => $b['Video']['ColorRange']  ?? null,
            ],
            'stream'     => [
                'encoder'     => $this->prettifyEncoder($encRaw),
                'rateControl' => $rc,
                'bitrateKbps' => $bitrate,
                'keyintSec'   => $keyint,
                'profile'     => $h264Profile,
            ],
            'audio'      => [
                'sampleRateHz' => isset($b['Audio']['SampleRate']) ? (int) $b['Audio']['SampleRate'] : null,
                'channels'     => $b['Audio']['ChannelSetup'] ?? null,
            ],
        ];
    }

    private function prettifyEncoder(string $id): ?string
    {
        $map = [
            'obs_x264' => 'x264', 'x264' => 'x264',
            'jim_nvenc' => 'NVENC H.264', 'ffmpeg_nvenc' => 'NVENC H.264',
            'obs_nvenc_h264_tex' => 'NVENC H.264', 'obs_nvenc_h264_soft' => 'NVENC H.264',
            'obs_nvenc_hevc_tex' => 'NVENC HEVC', 'obs_nvenc_hevc_soft' => 'NVENC HEVC',
            'obs_nvenc_av1_tex'  => 'NVENC AV1',  'obs_nvenc_av1_soft'  => 'NVENC AV1',
            'obs_qsv11'          => 'QSV',
        ];
        if ($id === '') return null;
        return $map[$id] ?? $id;
    }

    /** @return array<string, array<string, string>> */
    private function parseIni(string $path): array
    {
        $data = [];
        $section = null;
        foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
            $trim = trim($line);
            if ($trim === '' || str_starts_with($trim, ';') || str_starts_with($trim, '#')) continue;
            if (preg_match('/^\[(.+)\]$/', $trim, $m)) {
                $section = $m[1];
                $data[$section] = $data[$section] ?? [];
                continue;
            }
            if ($section !== null && str_contains($line, '=')) {
                [$k, $v] = explode('=', $line, 2);
                $data[$section][trim($k)] = trim($v);
            }
        }
        return $data;
    }
}
