<?php

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

class EntityCrudTest extends TestCase {
	private function create_areas_table(): void {
		app() -> thin_builder -> query("CREATE TABLE areas (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			alias VARCHAR(255),
			display_name VARCHAR(255),
			type VARCHAR(50),
			parent_id INTEGER DEFAULT 0,
			schema TEXT,
			create_at DATETIME,
			update_at DATETIME
		)");
	}

	private function drop_areas_table(): void {
		app() -> thin_builder -> query("DROP TABLE IF EXISTS areas");
	}

	private function seed_area(array $overrides = []): void {
		app() -> thin_builder -> insert('areas', array_merge([
			'alias' => 'test',
			'display_name' => 'Test',
			'type' => 'room',
			'parent_id' => 0,
			'schema' => '',
			'create_at' => '2024-01-01 10:00:00',
			'update_at' => '2024-01-01 10:00:00',
		], $overrides));
	}

	protected function setUp(): void {
		$this -> create_areas_table();
	}

	protected function tearDown(): void {
		$this -> drop_areas_table();
	}

	public function test_entity_update_persists_changes(): void {
		$this -> seed_area();

		$area = new Area(1);
		$area -> set('display_name', 'Updated Kitchen');
		$result = $area -> update();

		$this -> assertTrue($result);

		$rows = app() -> thin_builder -> select('areas', ['display_name'], [['id', '=', 1]]);
		$this -> assertSame('Updated Kitchen', $rows[0]['display_name']);
	}

	public function test_entity_get_returns_field_value(): void {
		$this -> seed_area(['alias' => 'bedroom', 'display_name' => 'Bedroom']);

		$area = new Area(1);
		$this -> assertSame('bedroom', $area -> get('alias'));
		$this -> assertSame('Bedroom', $area -> get('display_name'));
	}

	public function test_entity_update_returns_true_when_no_changes(): void {
		$this -> seed_area(['alias' => 'hall', 'display_name' => 'Hall']);

		$area = new Area(1);
		// No set() called
		$this -> assertTrue($area -> update());
	}

	public function test_entity_remove_deletes_row(): void {
		$this -> seed_area(['alias' => 'delete_me', 'display_name' => 'Delete Me']);

		$area = new Area(1);
		$method = new \ReflectionMethod($area, 'remove_entity');
		$method -> invoke($area);

		$rows = app() -> thin_builder -> select('areas', ['id'], [['id', '=', 1]]);
		$this -> assertCount(0, $rows);
	}

	public function test_entity_id_returns_correct_value(): void {
		$this -> seed_area();

		$area = new Area(1);
		$this -> assertSame(1, $area -> id());
	}

	public function test_entity_to_array_returns_data(): void {
		$this -> seed_area(['alias' => 'arr', 'display_name' => 'Array']);

		$area = new Area(1);
		$area -> fill();

		$arr = $area -> to_array();

		$this -> assertSame('arr', $arr['alias']);
		$this -> assertSame('Array', $arr['display_name']);
	}

	public function test_entity_update_returns_false_when_record_missing(): void {
		$this -> seed_area();

		$area = new Area(1);
		$area -> set('display_name', 'Gone');
		app() -> thin_builder -> delete('areas', ['id', '=', 1]);

		$this -> assertFalse($area -> update());
	}

	public function test_entity_update_preserves_modified_fields_on_exception(): void {
		$this -> seed_area();

		$area = new Area(1);
		$area -> set('display_name', 'Survivor');

		// Drop table to force a PDOException
		app() -> thin_builder -> query("DROP TABLE areas");

		try {
			$area -> update();
			$this -> fail('Expected PDOException was not thrown');
		} catch (\Exception $e) {
			$this -> assertStringContainsString('no such table', strtolower($e -> getMessage()));
		}

		// Re-create table so tearDown can clean up
		$this -> create_areas_table();

		// modified_fields should still contain the pending change
		$reflection = new \ReflectionClass($area);
		$prop = $reflection -> getProperty('modified_fields');
		$prop -> setAccessible(true);
		$modified = $prop -> getValue($area);

		$this -> assertArrayHasKey('display_name', $modified);
		$this -> assertSame('Survivor', $modified['display_name']);
	}

	public function test_entity_select_throws_on_missing_record(): void {
		$this -> expectException(\Exception::class);
		$this -> expectExceptionMessage("Record not found");

		$area = new Area(99999);
		$area -> get('alias');
	}
}
