<?php

namespace XcVm\Cli\Commands;

use XcVm\Cli\CommandInterface;
use XcVm\Cli\DaemonTrait;
use XcVm\Core\Config\SettingsManager;
use XcVm\Core\Config\SettingsRepository;
use XcVm\Core\Database\Database;
use XcVm\Core\Http\CurlClient;
use XcVm\Core\Util\StreamUtils;
use XcVm\Domain\Stream\StreamSorter;
use XcVm\Streaming\Codec\FfmpegPaths;
use XcVm\Streaming\Codec\FFprobeRunner;

/**
 * ScannerCommand — scanner command
 *
 * @package XC_VM_CLI_Commands
 * @author  Divarion_D <https://github.com/Divarion-D>
 * @copyright 2025-2026 Vateron Media
 * @link    https://github.com/Vateron-Media/XC_VM
 * @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
 */

require_once __DIR__ . '/../DaemonTrait.php';

class ScannerCommand implements CommandInterface {
	use DaemonTrait;

	public function getName(): string {
		return 'scanner';
	}

	public function getDescription(): string {
		return 'Daemon: scan on-demand sources';
	}

	public function execute(array $rArgs): int {
		if (!$this->assertRunAsXcVm()) {
			return 1;
		}

		global $db;

		$this->setProcessTitle('XC_VM[Scanner]');
		$this->killStaleProcesses('console.php scanner');
		$this->initDaemonMD5();

		if (!SettingsManager::getAll()['on_demand_checker']) {
			echo "On-Demand - Source Scanner is disabled.\n";
			return 0;
		}

		$this->rRefreshInterval = 60;

		while (true && $db && $db->ping()) {
			if (!$this->shouldRefreshSettings()) {
				// skip
			} else {
				if ($this->hasFileChanged()) {
					echo "File changed! Break.\n";
					break;
				}
				SettingsManager::set(SettingsRepository::getAll(true));
				$this->rLastCheck = time();
			}

			$this->scanOnDemandStreams($db);
			sleep(60);
			break;
		}

		if (is_object($db)) {
			$db->close_mysql();
		}

		$this->restartDaemon('scanner');
		return 0;
	}

	private function scanOnDemandStreams(Database $db): void {
		$rScanTime = SettingsManager::getAll()['on_demand_scan_time'] ?: 3600;

		if (!$db->query('SELECT `streams`.* FROM `streams` LEFT JOIN `streams_servers` ON `streams_servers`.`stream_id` = `streams`.`id` WHERE `streams_servers`.`pid` IS NULL AND `streams_servers`.`on_demand` = 1 AND `streams_servers`.`parent_id` IS NULL AND `streams`.`type` = 1 AND `streams`.`direct_source` = 0 AND `streams_servers`.`server_id` = ? AND (UNIX_TIMESTAMP() - (SELECT MAX(`date`) FROM `ondemand_check` WHERE `stream_id` = `streams`.`id` AND `server_id` = `streams_servers`.`server_id`) > ? OR (SELECT MAX(`date`) FROM `ondemand_check` WHERE `stream_id` = `streams`.`id` AND `server_id` = `streams_servers`.`server_id`) IS NULL);', SERVER_ID, $rScanTime)) {
			return;
		}

		if ($db->num_rows() <= 0) {
			return;
		}

		foreach ($db->get_rows() as $rRow) {
			echo '[' . $rRow['id'] . '] - ' . $rRow['stream_display_name'] . "\n";
			$db->query('SELECT t1.*, t2.* FROM `streams_options` t1, `streams_arguments` t2 WHERE t1.stream_id = ? AND t1.argument_id = t2.id', $rRow['id']);
			$rStreamArguments = $db->get_rows();
			$rProbesize = (intval($rRow['probesize_ondemand']) ?: 512000);
			$rAnalyseDuration = '10000000';
			$rTimeout = intval($rAnalyseDuration / 1000000) + SettingsManager::getAll()['probe_extra_wait'];

			if (SettingsManager::getAll()['on_demand_max_probe'] < $rTimeout && 0 < SettingsManager::getAll()['on_demand_max_probe']) {
				$rTimeout = intval(SettingsManager::getAll()['on_demand_max_probe']);
			}

			$rFFProbee = 'timeout ' . $rTimeout . ' ' . FfmpegPaths::probe() . ' {FETCH_OPTIONS} -probesize ' . $rProbesize . ' -analyzeduration ' . $rAnalyseDuration . ' -i {STREAM_SOURCE} -loglevel error -print_format json -show_streams -show_format 2>' . STREAMS_TMP_PATH . $rRow['id'] . '._errors';
			$rSources = json_decode($rRow['stream_source'], true);
			$rSourceID = 0;
			$rErrors = null;
			$rFFProbeOutput = null;
			$rTimeTaken = null;

			foreach ($rSources as $rSource) {
				$rProcessed = false;
				$rRealSource = $rSource;
				$rStreamSource = StreamUtils::parseStreamURL($rSource);
				echo 'Checking source: ' . $rSource . "\n";
				$rURLInfo = parse_url($rStreamSource);
				$rIsXC_VM = StreamUtils::detectXC_VM($rStreamSource);

				if ($rIsXC_VM && SettingsManager::getAll()['send_xc_vm_header']) {
					foreach (array_keys($rStreamArguments) as $rID) {
						if ($rStreamArguments[$rID]['argument_key'] != 'headers') {
							continue;
						}
						$rStreamArguments[$rID]['value'] .= "\r\n" . 'X-\XC_VM-Detect:1';
						$rProcessed = true;
					}
					if (!$rProcessed) {
						$rStreamArguments[] = array('value' => 'X-\XC_VM-Detect:1', 'argument_key' => 'headers', 'argument_cat' => 'fetch', 'argument_wprotocol' => 'http', 'argument_type' => 'text', 'argument_cmd' => "-headers '%s" . "\r\n" . "'");
					}
				}

				if ($rIsXC_VM && SettingsManager::getAll()['request_prebuffer'] == 1) {
					$rProcessed = false;
					foreach (array_keys($rStreamArguments) as $rID) {
						if ($rStreamArguments[$rID]['argument_key'] != 'headers') {
							continue;
						}
						$rStreamArguments[$rID]['value'] .= "\r\n" . 'X-\XC_VM-Prebuffer:1';
						$rProcessed = true;
					}
					if (!$rProcessed) {
						$rStreamArguments[] = array('value' => 'X-\XC_VM-Prebuffer:1', 'argument_key' => 'headers', 'argument_cat' => 'fetch', 'argument_wprotocol' => 'http', 'argument_type' => 'text', 'argument_cmd' => "-headers '%s" . "\r\n" . "'");
					}
				}

				$rProtocol = strtolower(substr($rStreamSource, 0, strpos($rStreamSource, '://')));
				$rFetchOptions = implode(' ', StreamUtils::getArguments($rStreamArguments, $rProtocol, 'fetch'));

				if ($rIsXC_VM && SettingsManager::getAll()['api_probe']) {
					$rProbeURL = $rURLInfo['scheme'] . '://' . $rURLInfo['host'] . ':' . $rURLInfo['port'] . '/probe/' . base64_encode($rURLInfo['path']);
					$rTime = round(microtime(true) * 1000);
					$rFFProbeOutput = json_decode(CurlClient::getURL($rProbeURL), true);
					$rTimeTaken = round(microtime(true) * 1000) - $rTime;
					if ($rFFProbeOutput && isset($rFFProbeOutput['streams'])) {
						echo "Got stream information via API\n";
						break;
					}
				}

				$rTime = round(microtime(true) * 1000);
				$rFFProbeOutput = json_decode(shell_exec(str_replace(array('{FETCH_OPTIONS}', '{STREAM_SOURCE}'), array($rFetchOptions, escapeshellarg($rStreamSource)), $rFFProbee)), true);
				$rTimeTaken = round(microtime(true) * 1000) - $rTime;

				if (file_exists(STREAMS_TMP_PATH . $rRow['id'] . '._errors') && 0 < filesize(STREAMS_TMP_PATH . $rRow['id'] . '._errors')) {
					if (!$rErrors && $rSourceID == 0) {
						$rErrors = file_get_contents(STREAMS_TMP_PATH . $rRow['id'] . '._errors');
					}
					@unlink(STREAMS_TMP_PATH . $rRow['id'] . '._errors');
				}

				if ($rFFProbeOutput && isset($rFFProbeOutput['streams'])) {
					echo "Got stream information via ffprobe\n";
					break;
				}

				if (!$rRow['llod']) {
					$rSourceID++;
				} else {
					break;
				}
			}

			if (!empty($rFFProbeOutput)) {
				echo "Source live!\n";
				$rFFProbeOutput = FFprobeRunner::parseFFProbe($rFFProbeOutput);
				$rAudioCodec = ($rFFProbeOutput['codecs']['audio']['codec_name'] ?: null);
				$rVideoCodec = ($rFFProbeOutput['codecs']['video']['codec_name'] ?: null);
				$rResolution = ($rFFProbeOutput['codecs']['video']['height'] ?: null);
				$rVideoBitrate = ($rFFProbeOutput['codecs']['video']['bit_rate'] ?: 0);
				$rAudioBitrate = ($rFFProbeOutput['codecs']['audio']['bit_rate'] ?: 0);
				$rFPS = (intval(explode('/', $rFFProbeOutput['codecs']['video']['r_frame_rate'])[0]) ?: 0);
				if ($rFPS == 0) {
					$rFPS = (intval(explode('/', $rFFProbeOutput['codecs']['video']['avg_frame_rate'])[0]) ?: 0);
				}
				if ($rFPS >= 1000) {
					$rFPS = intval($rFPS / 1000);
				}
				if ($rResolution) {
					$rResolution = StreamSorter::getNearest(array(240, 360, 480, 576, 720, 1080, 1440, 2160), $rResolution);
				}
				$rStatus = 1;
			} else {
				echo "Source down!\n";
				$rFPS = $rAudioCodec = $rVideoCodec = $rResolution = $rTimeTaken = null;
				$rSourceID = $rStatus = 0;
			}

			$rSource = $rSources[$rSourceID];
			$db->query('INSERT INTO `ondemand_check`(`stream_id`, `server_id`, `status`, `source_id`, `source_url`, `fps`, `video_codec`, `audio_codec`, `resolution`, `response`, `errors`, `date`) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);', $rRow['id'], SERVER_ID, $rStatus, $rSourceID, $rSource, $rFPS, $rVideoCodec, $rAudioCodec, $rResolution, $rTimeTaken, $rErrors, time());
			$db->query('UPDATE `streams_servers` SET `ondemand_check` = ? WHERE `stream_id` = ? AND `server_id` = ?;', $db->last_insert_id(), $rRow['id'], SERVER_ID);
			echo "\n";
		}
	}
}
