<?php

namespace SHServ\Models;

use \SHServ\Tools\DeviceScanner;
use \SHServ\Tools\DeviceAPI\Base;
use \SHServ\Entities\Device;
use \SHServ\Entities\DeviceAuth;

class Devices extends \SHServ\Middleware\Model {
	public function connect_new_device(String $device_ip, String $alias = "", String $name = "", String $description = "", ?Base $device_api = null): Device | Array {
		// validate device

		if ($device_api === null) {
			$device_api = new Base($device_ip);
		}
		$device_info = $device_api -> get_about();
		if(!$device_info["data"]) {
			return [
				"result" => false,
				"err_alias" => "device_not_found"
			];
		}

		if($device_info["data"]["status"] != "setup") {
			return [
				"result" => false,
				"err_alias" => "device_mode_error"
			];
		}

		// generate token
		$device_token = app() -> utils -> generate_token(16);

		try {
			app() -> thin_builder -> beginTransaction();

			$check_sql = "SELECT id FROM `" . Device::$table_name . "` WHERE `device_hard_id` = ? AND `status` = 'active'";
			if(in_array(FCONF['db']['dblib'] ?? '', ['mysql', 'mariadb'])) {
				$check_sql .= " FOR UPDATE";
			}
			$stmt = app() -> thin_builder -> prepare($check_sql);
			$stmt -> execute([$device_info["data"]["device_id"]]);
			if($stmt -> fetch(\PDO::FETCH_ASSOC)) {
				app() -> thin_builder -> rollBack();
				return [
					"result" => false,
					"err_alias" => "device_already_registered"
				];
			}

			// create in table devices
			$device_id = app() -> thin_builder -> insert(Device::$table_name, [
				"alias" => $alias,
				"name" => $name,
				"device_type" => $device_info["data"]["device_type"],
				"device_ip" => $device_info["data"]["ip_address"],
				"device_mac" => $device_info["data"]["mac_address"],
				"device_hard_id" => $device_info["data"]["device_id"],
				"firmware_version" => $device_info["data"]["firmware_version"],
				"connection_status" => "active",
				"status" => "active",
				"description" => $description,
				"last_contact" => date("Y-m-d H:i:s"),
				"create_at" => date("Y-m-d H:i:s")
			]);

			$device = $device_id ? new Device($device_id) : null;
			if(!$device) {
				throw new \Exception("Device insert failed");
			}

			// create in table device_auth
			$device_auth = app() -> thin_builder -> insert(DeviceAuth::$table_name, [
				"device_id" => $device -> id(),
				"device_token" => $device_token,
				"status" => "active",
				"create_at" => date("Y-m-d H:i:s")
			]);

			if(!$device_auth) {
				throw new \Exception("Device auth insert failed");
			}

			app() -> thin_builder -> commit();

			$device -> set_device_token($device_token);
			$device -> device_api() -> set_device_name($name);

			logging() -> info('php:Devices', 'Device created in DB', ['device_id' => $device -> id(), 'alias' => $device -> alias]);
			return $device;
		} catch (\Exception $e) {
			if(app() -> thin_builder -> inTransaction()) {
				app() -> thin_builder -> rollBack();
			}
			logging() -> error('php:Devices', 'Device creation failed: DB error', ['alias' => $alias, 'message' => $e -> getMessage()]);
			return [
				"result" => false,
				"err_alias" => "db_error"
			];
		}
	}

	public function resetup_device(Device $device) {
		$device_token = app() -> utils -> generate_token(16); 
		return $device -> resetup($device_token);
	}

	public function remove_device(int $device_id): Array {
		$device = $this -> by_id($device_id);

		if(!$device) {
			return [
				"result" => false,
				"err_alias" => "device_not_found"
			];
		}
		
		$device_auth = $device -> auth();
		if(!$device_auth) {
			return [
				"result" => false,
				"err_alias" => "device_not_found"
			];
		}

		$response = $device -> device_api() -> reset();
		$device_msg = "";

		$status = is_array($response["data"]) ? ($response["data"]["status"] ?? null) : null;
		if($response["http_code"] == 200 && $status == "ok") {
			$device_msg = $response["data"]["message"] ?? "";
		} else {
			$device_msg = FCONF['text_msgs']['device_removed_offline'] ?? 'Устройство не отвечает, удалено только из системы';
		}

		// status killed in device_auth table
		$device_auth -> kill();

		// remove from devices
		$device -> remove();

		logging() -> info('php:Devices', 'Device removed from DB', ['device_id' => $device_id, 'alias' => $device -> alias]);

		return [
			"result" => true,
			"device_msg" => $device_msg
		];
	}

	public function get_device_list(String $status = "active"): Array {
		// get from db table devices with status = "active"
		
		$raw_devices = app() -> thin_builder -> select(
			Device::$table_name, 
			Device::get_fields(), 
			[ ["status", "=", $status] ],
			[ "id" ],
			"DESC"
		);

		if(!$raw_devices) {
			return [];
		}

		// wrapped result to Device entity
		
		$devices = [];
		foreach($raw_devices as $i => $raw_device) {
			$devices[] = new Device($raw_device["id"], $raw_device);
		}

		return $devices;
	}

	public function get_unregistered_devices(): Array {
		return $this -> scanning_localnet(FCONF["device_ip_range"][0], FCONF["device_ip_range"][1], "setup");
	}

	public function scanning_localnet(String $from_ip, String $to_ip, String $status = ""): Array {
		$device_scanner = new DeviceScanner();
		$devices = $device_scanner -> scan_range($from_ip, $to_ip);
		if(!$status) {
			return $devices;
		}

		return $device_scanner -> filter_by_status($devices, $status);
	}

	public function by_hard_id(String $device_hard_id): Device | null {
		$results = app() -> thin_builder -> select(
			Device::$table_name,
			Device::get_fields(),
			[
				[ "device_hard_id", "=", $device_hard_id ],
				"AND",
				[ "status", "=", "active" ]
			]
		);

		if(!$results) {
			return null;
		}

		return new Device($results[0]["id"], $results[0]);
	}

	public function by_alias(String $alias): Device | null {
		$results = app() -> thin_builder -> select(
			Device::$table_name,
			Device::get_fields(),
			[
				[ "alias", "=", $alias ],
				"AND",
				[ "status", "=", "active" ]
			]
		);

		if(!$results) {
			return null;
		}

		return new Device($results[0]["id"], $results[0]);
	}

	public function by_id(int $device_id): Device | null {
		$results = app() -> thin_builder -> select(
			Device::$table_name,
			Device::get_fields(),
			[
				[ "id", "=", $device_id ],
				"AND",
				[ "status", "=", "active" ]
			]
		);

		if(!$results) {
			return null;
		}

		return new Device($results[0]["id"], $results[0]);
	}

	public function reboot_device(int $device_id): Array {
		$device = $this -> by_id($device_id);

		if(!$device) {
			return [
				"result" => false,
				"err_alias" => "device_not_found"
			];
		}

		$response = $device -> device_api() -> reboot();
		if($response["http_code"] != 200) {
			return [
				"result" => false,
				"err_alias" => "device_request_fail"
			];
		}

		if($response["data"]["status"] == "ok") {
			logging() -> info('php:Devices', 'Device rebooted', ['device_id' => $device_id, 'alias' => $device -> alias]);
			return [
				"result" => true,
				"msg" => $response["data"]["message"] ?? ""
			];
		}

		$err_type = $response["data"]["error"] ?? "undefined_error";
		$err_msg = $response["data"]["message"] ?? "Unknown error";

		return [
			"result" => false,
			"err_alias" => "device_request_fail",
			"msg" => "<b>{$err_type}</b>. {$err_msg}"
		];
	}

	public function get_device_info($device_id_or_alias): Array | null {
		if(is_string($device_id_or_alias)) {
			$device = $this -> by_alias($device_id_or_alias);
		} else {
			$device = $this -> by_id($device_id_or_alias);
		}

		if(!$device) {
			return null;
		}

		$about = $device -> device_api() -> get_about();
		$info = [
			"id" => $device -> id(),
			"alias" => $device -> alias,
			"name" => $device -> name,
			"description" => $device -> description,
			"status" => $device -> status,
			"connection_status" => $device -> connection_status,
			"last_contact" => $device -> last_contact,
			"create_at" => $device -> create_at,
			"device" => $about["data"] ?? []
		];

		return $info;
	}

	public function alias_is_uniq(String $alias, ?int $exclude_id = null): bool {
		$where = [ ["alias", "=", $alias], "AND", ["status", "!=", "removed"] ];
		if ($exclude_id !== null) {
			$where[] = "AND";
			$where[] = ["id", "!=", $exclude_id];
		}

		$count = app() -> thin_builder -> count(
			Device::$table_name,
			$where
		);

		return $count ? false : true;
	}

	/**
	 * Reconcile scan results against active devices in DB.
	 * For each active device whose hard_id was found on the network,
	 * update device_ip if it changed and restore connection_status to active.
	 * Returns statistics about updates.
	 *
	 * @param array $found_devices Results from DeviceScanner::scan_range()
	 * @return array ['updated' => int, 'restored' => int]
	 */
	public function reconcile_scan_results(array $found_devices): array {
		$active_devices = $this -> get_device_list("active");

		$found_by_hard_id = [];
		foreach($found_devices as $found) {
			$hard_id = $found["device_id"] ?? null;
			if($hard_id) {
				$found_by_hard_id[(string)$hard_id] = $found;
			}
		}

		$updated = 0;
		$restored = 0;

		foreach($active_devices as $device) {
			if(!isset($found_by_hard_id[(string)$device -> device_hard_id])) {
				continue;
			}

			$found = $found_by_hard_id[(string)$device -> device_hard_id];
			$is_changed = false;

			if($device -> device_ip != ($found["ip_address"] ?? "")) {
				$device -> device_ip = $found["ip_address"] ?? "";
				$is_changed = true;
				$updated++;
			}

			if($device -> connection_status != "active") {
				$device -> connection_status = "active";
				$is_changed = true;
				$restored++;
			}

			if($is_changed) {
				$device -> update();
			}

			// HTTP-ответ от устройства = контакт
			$device -> touch_last_contact();
		}

		logging() -> info('php:Devices', 'Scan results reconciled', ['updated' => $updated, 'restored' => $restored]);

		return [
			"updated" => $updated,
			"restored" => $restored,
		];
	}
}
