<?php

namespace Models;

/**
 * Representation of a User from the auth database. Contains auth-related info and handles meta-level state.
 */
class User
{
	/**
	 * Auto-incrementing unique ID.
	 */
	public int $id;

	/**
	 * A presumably unique username.
	 */
	public string $username;

	/**
	 * A presumably unique email address.
	 */
	public string $email;

	/**
	 * The user's hashed password; private to protect it from inspection.
	 */
	private string $password;

	/**
	 * Auth level; see Auth to check how permissions work.
	 */
	public int $auth;

	/**
	 * Currently selected character's ID.
	 */
	public int $char_id;

	/**
	 * How many characters the user can have.
	 */
	public int $char_slots;

	/**
	 * When this account was created (registered date).
	 */
	public int $created;

	/**
	 * When the account was last logged in to.
	 */
	public int $last_login;

	/**
	 * Populate a User object with data; assumes you are passing the associatve array from SQLite directly.
	 */
	public function __construct(array $data)
	{
		foreach ($data as $k => $v) {
			if (property_exists($this, $k)) {
				$this->$k = in_array($k, ['created', 'last_login']) ? strtotime($v) : $v;
			}
		}
	}

	/**
	 * Find a user by their username, email or ID. Case-nonsensitive. Throws an Exception when the query is
	 * badly formed. Returns false if no user is found. Returns a User on success.
	 */
	public static function find(string|int $identifier): User|false
	{
		$r = auth_db()->query(
			"SELECT * FROM users WHERE username = :i COLLATE NOCASE OR email = :i COLLATE NOCASE OR id = :i LIMIT 1",
			[':i' => $identifier]
		);
		if ($r === false) throw new \Exception("Failed to query user. (U::f)"); // badly formed query
		$u = $r->fetchArray(SQLITE3_ASSOC);
		if ($u === false) return false; // no user found
		return new User($u);
	}

	/**
	 * Create a new user row in the database. This function does not check for the unique-ness or validity
	 * of the username or password passed to it; that is the responsibility of the caller. Returns false on
	 * failure.
	 */
	public static function create(string $username, string $email, string $password, int $auth = 0): \SQLite3Result|false
	{
		return auth_db()->query("INSERT INTO users (username, email, password, auth) VALUES (:u, :e, :p, :a)", [
			':u' => $username,
			':e' => $email,
			':p' => password_hash($password, PASSWORD_ARGON2ID),
			':a' => $auth
		]);
	}

	/**
	 * Check $ref against the user's password.
	 */
	public function check_password(string $ref): bool
	{
		return password_verify($ref, $this->password);
	}

	/**
	 * Check if the given username exists.
	 */
	public static function username_exists(string $username): bool
	{
		return auth_db()->exists('users', 'username', $username);
	}

	/**
	 * Check if the given email exists.
	 */
	public static function email_exists(string $email): bool
	{
		return auth_db()->exists('users', 'email', $email);
	}

	/**
	 * Delete a user by their username, email, or id.
	 */
	public static function delete(string|int $identifier): \SQLite3Result|false
	{
		return auth_db()->query(
			"DELETE FROM users WHERE username = :i OR email = :i OR id = :i",
			[':i' => $identifier]
		);
	}

	/**
	 * Return a count of how many characters this user has.
	 */
	public function char_count(): int
	{
		$c = live_db()->query(
			"SELECT COUNT(*) FROM characters WHERE user_id = :u",
			[':u' => $this->id]
		)->fetchArray(SQLITE3_NUM);
		if ($c === false) throw new \Exception('Failed to count characters. (U::cc)');
		return (int) $c[0];
	}

	/**
	 * Get a an array of id => [name, level] for all characters associated with this user ID. Returns false
	 * if there are no characters.
	 */
	public function char_list(): array|false
	{
		$q = live_db()->query("SELECT id, name, level FROM characters WHERE user_id = ?", [$this->id]);
		if ($q === false) throw new \Exception('Failed to list characters. (U->cl)');

		$c = [];
		while ($row = $q->fetchArray(SQLITE3_ASSOC)) {
			$c[$row['id']] = ['name' => $row['name'], 'level' => $row['level']];
		}

		// return false if no characters
		return empty($c) ? false : $c;
	}
}
