<?php

use PHPUnit\Framework\TestCase;
use SHServ\Controllers\CronController;
use SHServ\Middleware\ControlScripts;
use SHServ\Tools\DeviceScanner;

class TestableDeviceScanner extends DeviceScanner {
	public $mockResults = [];
	public function scan_range(string $start_ip, string $end_ip, int $port = 80, float $timeout = 1): array {
		return $this -> mockResults;
	}
}

class TestableCronController extends CronController {
	public $mockScannerResults = [];

	protected function createDeviceScanner(): DeviceScanner {
		$scanner = new TestableDeviceScanner();
		$scanner -> mockResults = $this -> mockScannerResults;
		return $scanner;
	}
}

class CronControllerTest extends TestCase {
	protected $tb;

	protected function setUp(): void {
		$this -> tb = app() -> thin_builder;
		$this -> tb -> query("CREATE TABLE scripts (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			area_id INTEGER DEFAULT 0,
			uniq_name TEXT,
			type TEXT,
			state TEXT,
			params_schema TEXT,
			create_at TEXT,
			update_at TEXT
		)");
		$this -> tb -> query("CREATE TABLE devices (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			area_id INTEGER DEFAULT 0,
			alias TEXT,
			name TEXT,
			device_type TEXT,
			device_ip TEXT,
			device_mac TEXT,
			device_hard_id TEXT,
			firmware_version TEXT,
			connection_status TEXT,
			status TEXT,
			description TEXT,
			last_contact TEXT,
			create_at TEXT,
			update_at TEXT
		)");
		if(!isset(app() -> scripts_registry)) {
			app() -> scripts_registry = new \SHServ\Middleware\ScriptsRegistry();
		}
		ControlScripts::flush_statics();
	}

	protected function tearDown(): void {
		$this -> tb -> query("DROP TABLE IF EXISTS scripts");
		$this -> tb -> query("DROP TABLE IF EXISTS devices");
		ControlScripts::flush_statics();
	}

	public function test_runs_enabled_regular_scripts(): void {
		$ran = [];

		app() -> scripts_registry -> regular['fail_script'] = [
			'attributes' => ['alias' => 'fail_script', 'name' => 'Fail'],
			'code' => '',
			'script' => function() use (&$ran) { $ran[] = 'fail_script'; throw new \Exception('boom'); }
		];
		app() -> scripts_registry -> regular['ok_script'] = [
			'attributes' => ['alias' => 'ok_script', 'name' => 'OK'],
			'code' => '',
			'script' => function() use (&$ran) { $ran[] = 'ok_script'; }
		];

		$this -> tb -> insert('scripts', [
			'uniq_name' => 'fail_script',
			'type' => 'regular',
			'state' => 'enabled',
			'create_at' => date('Y-m-d H:i:s'),
		]);
		$this -> tb -> insert('scripts', [
			'uniq_name' => 'ok_script',
			'type' => 'regular',
			'state' => 'enabled',
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
		$controller = new TestableCronController();
		$controller -> run_regular_cron_scripts();

		$this -> assertSame(['fail_script', 'ok_script'], $ran);
	}

	public function test_disabled_regular_scripts_are_skipped(): void {
		$ran = false;

		app() -> scripts_registry -> regular['disabled_script'] = [
			'attributes' => ['alias' => 'disabled_script', 'name' => 'Disabled'],
			'code' => '',
			'script' => function() use (&$ran) { $ran = true; }
		];

		$this -> tb -> insert('scripts', [
			'uniq_name' => 'disabled_script',
			'type' => 'regular',
			'state' => 'disabled',
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
		$controller = new TestableCronController();
		$controller -> run_regular_cron_scripts();

		$this -> assertFalse($ran);
	}

	public function test_marks_stale_devices_as_lost(): void {
		$this -> tb -> insert('devices', [
			'alias' => 'stale_device',
			'name' => 'Stale Device',
			'device_type' => 'relay',
			'device_ip' => '192.168.1.10',
			'device_hard_id' => 'hard_stale_001',
			'connection_status' => 'active',
			'status' => 'active',
			'last_contact' => date('Y-m-d H:i:s', time() - 600), // 10 минут назад
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
		$controller = new TestableCronController();
		$controller -> mockScannerResults = [];
		$controller -> status_update_scanning();

		$row = $this -> tb -> query("SELECT connection_status FROM devices WHERE device_hard_id = 'hard_stale_001'") -> fetch(\PDO::FETCH_ASSOC);
		$this -> assertSame('lost', $row['connection_status']);
	}

	public function test_keeps_fresh_devices_active(): void {
		$this -> tb -> insert('devices', [
			'alias' => 'fresh_device',
			'name' => 'Fresh Device',
			'device_type' => 'relay',
			'device_ip' => '192.168.1.20',
			'device_hard_id' => 'hard_fresh_001',
			'connection_status' => 'active',
			'status' => 'active',
			'last_contact' => date('Y-m-d H:i:s', time() - 60), // 1 минуту назад
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
		$controller = new TestableCronController();
		$controller -> mockScannerResults = [];
		$controller -> status_update_scanning();

		$row = $this -> tb -> query("SELECT connection_status FROM devices WHERE device_hard_id = 'hard_fresh_001'") -> fetch(\PDO::FETCH_ASSOC);
		$this -> assertSame('active', $row['connection_status']);
	}

	public function test_scanner_recovers_lost_device(): void {
		$this -> tb -> insert('devices', [
			'alias' => 'lost_device',
			'name' => 'Lost Device',
			'device_type' => 'relay',
			'device_ip' => '192.168.1.30',
			'device_hard_id' => 'hard_lost_001',
			'connection_status' => 'lost',
			'status' => 'active',
			'last_contact' => date('Y-m-d H:i:s', time() - 600),
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
		$controller = new TestableCronController();
		$controller -> mockScannerResults = [
			[
				'device_id' => 'hard_lost_001',
				'ip_address' => '192.168.1.30',
				'device_type' => 'relay',
			]
		];
		$controller -> status_update_scanning();

		$row = $this -> tb -> query("SELECT connection_status, device_ip FROM devices WHERE device_hard_id = 'hard_lost_001'") -> fetch(\PDO::FETCH_ASSOC);
		$this -> assertSame('active', $row['connection_status']);
		$this -> assertSame('192.168.1.30', $row['device_ip']);
	}

	public function test_scanner_updates_ip_on_roaming(): void {
		$this -> tb -> insert('devices', [
			'alias' => 'roaming_device',
			'name' => 'Roaming Device',
			'device_type' => 'relay',
			'device_ip' => '192.168.1.40',
			'device_hard_id' => 'hard_roam_001',
			'connection_status' => 'active',
			'status' => 'active',
			'last_contact' => date('Y-m-d H:i:s', time() - 60),
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
		$controller = new TestableCronController();
		$controller -> mockScannerResults = [
			[
				'device_id' => 'hard_roam_001',
				'ip_address' => '192.168.1.99',
				'device_type' => 'relay',
			]
		];
		$controller -> status_update_scanning();

		$row = $this -> tb -> query("SELECT connection_status, device_ip FROM devices WHERE device_hard_id = 'hard_roam_001'") -> fetch(\PDO::FETCH_ASSOC);
		$this -> assertSame('active', $row['connection_status']);
		$this -> assertSame('192.168.1.99', $row['device_ip']);
	}
}
