<?php

use PHPUnit\Framework\TestCase;
use SHServ\Entities\Area;

class AreaRecursionTest extends TestCase {
	protected $tb;

	protected function setUp(): void {
		$this -> tb = app() -> thin_builder;

		$this -> tb -> query("CREATE TABLE areas (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			alias TEXT,
			display_name TEXT,
			type TEXT,
			parent_id INTEGER DEFAULT 0,
			schema TEXT,
			create_at TEXT,
			update_at TEXT
		)");

		$this -> tb -> query("CREATE TABLE devices (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			area_id INTEGER,
			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
		)");

		$this -> tb -> query("CREATE TABLE scripts (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			area_id INTEGER,
			uniq_name TEXT,
			type TEXT,
			state TEXT,
			params_schema TEXT,
			create_at TEXT,
			update_at TEXT
		)");
	}

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

	protected function createArea(string $alias, int $parentId = 0, string $type = 'room'): int {
		return $this -> tb -> insert('areas', [
			'alias' => $alias,
			'display_name' => ucfirst($alias),
			'type' => $type,
			'parent_id' => $parentId,
			'schema' => '',
			'create_at' => date('Y-m-d H:i:s'),
		]);
	}

	protected function createDevice(int $areaId, string $alias): int {
		return $this -> tb -> insert('devices', [
			'area_id' => $areaId,
			'alias' => $alias,
			'name' => ucfirst($alias),
			'device_type' => 'relay',
			'status' => 'active',
			'create_at' => date('Y-m-d H:i:s'),
		]);
	}

	protected function createScript(int $areaId, string $uniqName, string $type = 'regular'): int {
		return $this -> tb -> insert('scripts', [
			'area_id' => $areaId,
			'uniq_name' => $uniqName,
			'type' => $type,
			'state' => 'enable',
			'create_at' => date('Y-m-d H:i:s'),
		]);
	}

	public function test_get_inner_areas_non_recursive(): void {
		$root = $this -> createArea('root', 0, 'building');
		$this -> createArea('floor', $root, 'floor');
		$this -> createArea('room', $root, 'room');
		$this -> createArea('closet', $root + 1, 'room'); // child of floor

		$area = new Area($root);
		$inner = $area -> get_inner_areas(false);

		$aliases = array_map(fn($a) => $a -> alias, $inner);
		$this -> assertSame(['floor', 'room'], $aliases);
	}

	public function test_get_inner_areas_recursive(): void {
		$root = $this -> createArea('root', 0, 'building');
		$floor = $this -> createArea('floor', $root, 'floor');
		$this -> createArea('room1', $floor, 'room');
		$this -> createArea('room2', $floor, 'room');

		$area = new Area($root);
		$inner = $area -> get_inner_areas(true);

		$aliases = array_map(fn($a) => $a -> alias, $inner);
		$this -> assertSame(['floor', 'room1', 'room2'], $aliases);
	}

	public function test_get_recursive_inner_areas_ids_respects_depth_limit(): void {
		$ids = [];
		$parentId = 0;
		for ($i = 0; $i < 13; $i++) {
			$parentId = $this -> createArea("area_{$i}", $parentId, 'room');
			$ids[] = $parentId;
		}

		$rootArea = new Area($ids[0]);
		$method = new \ReflectionMethod($rootArea, 'get_recursive_inner_areas_ids');
		$result = $method -> invoke($rootArea, $ids[0], 0);

		// Depth limit 10 means descendants up to level 10 are collected
		// Chain: 0 -> 1 -> 2 -> ... -> 12
		// Expected IDs: 1..10 (10 items)
		$expected = array_slice($ids, 1, 10);
		$this -> assertSame($expected, $result);
		$this -> assertNotContains($ids[11], $result);
		$this -> assertNotContains($ids[12], $result);
	}

	public function test_remove_clears_child_parent_ids(): void {
		$root = $this -> createArea('root', 0, 'building');
		$child = $this -> createArea('child', $root, 'floor');

		$rootArea = new Area($root);
		$rootArea -> remove();

		$rows = $this -> tb -> select('areas', ['parent_id'], [['id', '=', $child]]);
		$this -> assertSame(0, $rows[0]['parent_id']);
	}

	public function test_remove_clears_device_area_ids(): void {
		$room = $this -> createArea('room', 0, 'room');
		$device = $this -> createDevice($room, 'relay_1');

		$roomArea = new Area($room);
		$roomArea -> remove();

		$rows = $this -> tb -> select('devices', ['area_id'], [['id', '=', $device]]);
		$this -> assertSame(0, $rows[0]['area_id']);
	}

	public function test_get_areas_by_type_recursive(): void {
		$root = $this -> createArea('root', 0, 'building');
		$floor = $this -> createArea('floor', $root, 'floor');
		$this -> createArea('room1', $floor, 'room');
		$this -> createArea('room2', $floor, 'room');

		$area = new Area($root);
		$floors = $area -> get_areas_by_type('floor', true);
		$rooms = $area -> get_areas_by_type('room', true);

		$this -> assertCount(1, $floors);
		$this -> assertSame('floor', $floors[0] -> alias);
		$this -> assertCount(2, $rooms);
	}

	public function test_get_area_by_alias_recursive(): void {
		$root = $this -> createArea('root', 0, 'building');
		$floor = $this -> createArea('floor', $root, 'floor');
		$room = $this -> createArea('room1', $floor, 'room');

		$area = new Area($root);
		$found = $area -> get_area_by_alias('room1', true);

		$this -> assertInstanceOf(Area::class, $found);
		$this -> assertSame($room, $found -> id());
	}

	public function test_get_inner_devices_recursive(): void {
		$root = $this -> createArea('root', 0, 'building');
		$floor = $this -> createArea('floor', $root, 'floor');
		$room = $this -> createArea('room', $floor, 'room');
		$device = $this -> createDevice($room, 'relay_1');

		$area = new Area($root);
		$devices = $area -> get_inner_devices(true);

		$this -> assertCount(1, $devices);
		$this -> assertSame($device, $devices[0] -> id());
	}

	public function test_get_inner_scripts_recursive(): void {
		$root = $this -> createArea('root', 0, 'building');
		$floor = $this -> createArea('floor', $root, 'floor');
		$room = $this -> createArea('room', $floor, 'room');
		$script = $this -> createScript($room, 'script_1');

		$area = new Area($root);
		$scripts = $area -> get_inner_scripts(true);

		$this -> assertCount(1, $scripts);
		$this -> assertSame($script, $scripts[0] -> id());
	}

	public function test_parent_area_returns_parent_or_null(): void {
		$root = $this -> createArea('root', 0, 'building');
		$child = $this -> createArea('child', $root, 'floor');

		$rootArea = new Area($root);
		$childArea = new Area($child);

		$this -> assertNull($rootArea -> parent_area());
		$this -> assertInstanceOf(Area::class, $childArea -> parent_area());
		$this -> assertSame($root, $childArea -> parent_area() -> id());
	}
}
