<?php

/*
TODO:
 + Создание метода для получения state скрипта
 + Вывод списков скриптов с полем state
 + Интегрировать поведение относительно поля state при запуске скриптов. Выключенные скрипты не должны быть запущены.
 */

namespace SHServ\Controllers;

use \SHServ\Middleware\ControlScripts;
use \SHServ\Models\Scripts;
use \SHServ\Entities\Script;
use \SHServ\Entities\Area;
use \SHServ\Integrations\GAuth\AuthControllerTrait;

use \SHServ\Helpers\ScriptParamsValidator;

class ScriptsRESTAPIController extends \SHServ\Middleware\Controller {
	use AuthControllerTrait;
	public function run_action_script($alias, $params) {
		if ($auth = $this -> require_permission('scripts.run')) { return $auth; }
		if(is_string($params)) {
			$decoded = json_decode($params, true);
			if(json_last_error() !== JSON_ERROR_NONE) {
				logging() -> warn('php:Scripts', 'Run action script failed: invalid JSON params', ['alias' => $alias]);
				return $this -> utils() -> response_error("invalid_json", ["params"]);
			}
			$params = $decoded;
		}

		if(!is_array($params)) {
			logging() -> warn('php:Scripts', 'Run action script failed: invalid params', ['alias' => $alias]);
			return $this -> utils() -> response_error("invalid_params", ["params"]);
		}

		if(!preg_match('/^[a-z0-9_]+$/', $alias) || strlen($alias) > 255) {
			logging() -> warn('php:Scripts', 'Run action script failed: invalid alias', ['alias' => $alias]);
			return $this -> utils() -> response_error("invalid_alias", ["alias"]);
		}

		// Валидация параметров по schema
		$reg = app() -> scripts_registry;
		$schema = $reg -> actions[$alias]["params_schema"] ?? null;
		$validation = \SHServ\Helpers\ScriptParamsValidator::validate($schema, $params);

		if(!$validation["ok"]) {
			$failed_fields = array_column($validation["errors"], "field");
			logging() -> warn('php:Scripts', 'Run action script failed: invalid params', ['alias' => $alias, 'errors' => $validation["errors"]]);
			return $this -> utils() -> response_error("invalid_params", $failed_fields);
		}

		$params = $validation["params"];

		logging() -> info('php:Scripts', 'Run action script', ['alias' => $alias]);

		$result = ControlScripts::run_action_script($alias, $params);

		if(!$result) {
			logging() -> warn('php:Scripts', 'Run action script failed: not found or disabled', ['alias' => $alias]);
			return $this -> utils() -> response_error("action_script_not_found");
		}

		logging() -> info('php:Scripts', 'Run action script successful', ['alias' => $alias, 'exec_time' => $result['exec_time'] ?? null]);
		return $this -> utils() -> response_success([
			"return" => $result
		]);
	}

	public function actions_scripts_list() {
		if ($auth = $this -> require_permission('scripts.view')) { return $auth; }
		$data = (new Scripts()) -> actions_scripts_list();

		logging() -> trace('php:Scripts', 'Action scripts list fetched', ['total' => count($data)]);

		return $this -> utils() -> response_success([
			"scripts" => $data,
			"total" => count($data)
		]);
	}

	public function regular_scripts_list() {
		if ($auth = $this -> require_permission('scripts.view')) { return $auth; }
		$data = (new Scripts()) -> regular_scripts_list();

		logging() -> trace('php:Scripts', 'Regular scripts list fetched', ['total' => count($data)]);

		return $this -> utils() -> response_success([
			"scripts" => $data,
			"total" => count($data)
		]);
	}

	public function scope_list() {
		if ($auth = $this -> require_permission('scripts.view')) { return $auth; }
		$scripts_model = new Scripts();
		$scopes = $scripts_model -> get_scopes_list();

		logging() -> trace('php:Scripts', 'Scope list fetched', ['total' => count($scopes)]);

		return $this -> utils() -> response_success([
			"scopes" => $scopes,
			"total" => count($scopes)
		]);
	}

	public function scope_file($name) {
		if ($auth = $this -> require_permission('scripts.view')) { return $auth; }
		$scripts_model = new Scripts();
		$scopes = $scripts_model -> get_scopes_list();

		$file = "";
		foreach($scopes as $scope) {
			if($name != $scope["name"]) {
				continue;
			}

			$file = file_get_contents($scope["path"] . "/" . $scope["filename"]);

			break;
		}

		if(!$file) {
			return $this -> utils() -> response_error("scope_not_found");
		}

		return $this -> utils() -> response_success(["source" => $file]);
	}

	public function scope_remove($name) {
		if ($auth = $this -> require_permission('scripts.edit')) { return $auth; }
		logging() -> warn('php:Scripts', 'Remove scope', ['scope_name' => $name]);
		return (new Scripts()) -> remove_scope($name)
			? $this -> utils() -> response_success()
			: $this -> utils() -> response_error("undefined_error");
	}

	protected function set_script_state(String $type, String $uniq_name, String $state) {
		if ($auth = $this -> require_permission('scripts.edit')) { return $auth; }
		if(!in_array($state, ["enable", "disable"], true)) {
			logging() -> warn('php:Scripts', 'Set script state failed: invalid state', ['type' => $type, 'alias' => $uniq_name, 'state' => $state]);
			return $this -> utils() -> response_error("invalid_state", ["state"]);
		}

		if($state == "enable") {
			$result = (new Scripts()) -> enable_script($type, $uniq_name);
		} else {
			$result = (new Scripts()) -> disable_script($type, $uniq_name);
		}

		if ($result === true) {
			logging() -> info('php:Scripts', 'Script state changed', ['type' => $type, 'alias' => $uniq_name, 'state' => $state]);
			return $this -> utils() -> response_success();
		}

		logging() -> warn('php:Scripts', 'Set script state failed', ['type' => $type, 'alias' => $uniq_name, 'state' => $state, 'error' => $result]);
		return $this -> utils() -> response_error($result);
	}

	public function set_scope_state($uniq_name, $state) {
		return $this -> set_script_state("scope", $uniq_name, $state);
	}

	public function set_regular_script_state($uniq_name, $state) {
		return $this -> set_script_state("regular", $uniq_name, $state);
	}

	public function set_action_script_state($uniq_name, $state) {
		return $this -> set_script_state("action", $uniq_name, $state);
	}

	public function place_in_area($target_id, $place_in_area_id) {
		if ($auth = $this -> require_permission('scripts.edit')) { return $auth; }
		if($target_id != intval($target_id) or intval($target_id) < 1) {
			return $this -> utils() -> response_error("invalid_id", ["target_id"]);
		}

		if($place_in_area_id != intval($place_in_area_id) or intval($place_in_area_id) < 1) {
			return $this -> utils() -> response_error("invalid_id", ["place_in_area_id"]);
		}

		$script = new Script(intval($target_id));
		$place_area = new Area(intval($place_in_area_id));

		if(!$script) {
			logging() -> warn('php:Scripts', 'Place script in area failed: not found', ['script_id' => $target_id]);
			return $this -> utils() -> response_error("script_not_exists");
		}

		if(!$place_area) {
			logging() -> warn('php:Scripts', 'Place script in area failed: area not found', ['area_id' => $place_in_area_id]);
			return $this -> utils() -> response_error("area_not_exists");
		}

		logging() -> info('php:Scripts', 'Place script in area', ['script_id' => $script -> id(), 'alias' => $script -> uniq_name, 'area_id' => $place_area -> id()]);

		if(!$script -> place_in_area($place_area)) {
			logging() -> warn('php:Scripts', 'Place script in area failed', ['script_id' => $script -> id(), 'area_id' => $place_area -> id()]);
			return $this -> utils() -> response_error("undefined_error");
		}

		return $this -> utils() -> response_success();
	}

	public function scope_update($name, $source) {
		if ($auth = $this -> require_permission('scripts.edit')) { return $auth; }

		if(!preg_match('/^[A-Za-z0-9_]+$/', $name) || strlen($name) > 255) {
			logging() -> warn('php:Scripts', 'Scope update failed: invalid name', ['name' => $name]);
			return $this -> utils() -> response_error("invalid_alias", ["name"]);
		}

		if(!is_string($source) || empty($source)) {
			logging() -> warn('php:Scripts', 'Scope update failed: empty source', ['name' => $name]);
			return $this -> utils() -> response_error("invalid_source", ["source"]);
		}

		// Validate syntax via php -l
		$tmpFile = tempnam(sys_get_temp_dir(), 'scope_');
		file_put_contents($tmpFile, $source);
		$output = [];
		$returnCode = 0;
		exec("php -l " . escapeshellarg($tmpFile) . " 2>&1", $output, $returnCode);
		unlink($tmpFile);

		if($returnCode !== 0) {
			logging() -> warn('php:Scripts', 'Scope update failed: syntax error', ['name' => $name]);
			return $this -> utils() -> response_error("syntax_error", ["source"], implode("\n", $output));
		}

		// Validate namespace
		if(strpos($source, 'namespace ControlScripts\\Scopes;') === false) {
			logging() -> warn('php:Scripts', 'Scope update failed: invalid namespace', ['name' => $name]);
			return $this -> utils() -> response_error("invalid_namespace", ["source"]);
		}

		// Validate class name
		if(strpos($source, "class {$name}") === false) {
			logging() -> warn('php:Scripts', 'Scope update failed: class name mismatch', ['name' => $name]);
			return $this -> utils() -> response_error("class_name_mismatch", ["source"]);
		}

		$targetPath = __DIR__ . "/../../../automation/Scopes/{$name}.php";

		// Backup existing file
		if(file_exists($targetPath)) {
			$backupPath = $targetPath . ".bak." . time();
			copy($targetPath, $backupPath);
		}

		if(file_put_contents($targetPath, $source) === false) {
			logging() -> error('php:Scripts', 'Scope update failed: write error', ['name' => $name, 'path' => $targetPath]);
			return $this -> utils() -> response_error("write_failed");
		}

		// Invalidate opcache so next request picks up the new file
		if(function_exists('opcache_invalidate')) {
			opcache_invalidate($targetPath, true);
		}

		logging() -> info('php:Scripts', 'Scope updated', ['name' => $name, 'path' => $targetPath]);

		return $this -> utils() -> response_success();
	}

	public function timers_list() {
		if ($auth = $this -> require_permission('scripts.view')) { return $auth; }
		$timers_model = new \SHServ\Models\Timers();
		$timers = $timers_model -> get_timers();

		logging() -> trace('php:Scripts', 'Timers list fetched', ['total' => count($timers)]);

		return $this -> utils() -> response_success([
			"timers" => $timers,
			"total" => count($timers)
		]);
	}

	public function timers_cancel($timer_alias) {
		if ($auth = $this -> require_permission('scripts.edit')) { return $auth; }

		if(!preg_match('/^[a-z0-9_]+$/', $timer_alias) || strlen($timer_alias) > 255) {
			logging() -> warn('php:Scripts', 'Timer cancel failed: invalid alias', ['timer_alias' => $timer_alias]);
			return $this -> utils() -> response_error("invalid_alias", ["timer_alias"]);
		}

		$timers_model = new \SHServ\Models\Timers();
		$result = $timers_model -> cancel_by_alias($timer_alias);

		if(!$result) {
			logging() -> warn('php:Scripts', 'Timer cancel failed', ['timer_alias' => $timer_alias]);
			return $this -> utils() -> response_error("timer_not_found");
		}

		logging() -> info('php:Scripts', 'Timer cancelled', ['timer_alias' => $timer_alias]);
		return $this -> utils() -> response_success();
	}

	public function unassign_from_area($target_id) {
		if ($auth = $this -> require_permission('scripts.edit')) { return $auth; }
		if($target_id != intval($target_id) or intval($target_id) < 1) {
			return $this -> utils() -> response_error("invalid_id", ["target_id"]);
		}

		$script = new Script(intval($target_id));

		if(!$script) {
			logging() -> warn('php:Scripts', 'Unassign script from area failed: not found', ['script_id' => $target_id]);
			return $this -> utils() -> response_error("script_not_exists");
		}

		logging() -> info('php:Scripts', 'Unassign script from area', ['script_id' => $script -> id(), 'alias' => $script -> uniq_name]);

		return $script -> place_in_area_id(0)
			? $this -> utils() -> response_success()
			: $this -> utils() -> response_error("undefined_error");
	}
}