<?php

namespace app;

class Admin
{
    const int ADMIN_SESSION_TIMEOUT = 86400 * 14;
    const string ADMIN_COOKIE_NAME = 'admin_key';
    const int ADMIN_LOGIN_MAX_LENGTH = 32;

    // session data
    protected static ?int $id = null;
    protected static ?int $authId = null;
    protected static ?string $csrfSalt = null;
    protected static ?string $login = null;

    public static function exists(string $login): bool {
        $db = getDB();
        return (int)$db->result($db->query("SELECT COUNT(*) FROM admins WHERE login=? LIMIT 1", $login)) > 0;
    }

    public static function add(string $login, string $password): int {
        $db = getDB();
        $db->insert('admins', [
            'login' => $login,
            'password' => saltPassword($password),
            'activity_ts' => 0
        ]);
        return $db->insertId();
    }

    public static function delete(string $login): bool {
        $db = getDB();
        $id = self::getIdByLogin($login);
        if (!$db->query("DELETE FROM admins WHERE login=?", $login)) return false;
        if (!$db->query("DELETE FROM admin_auth WHERE admin_id=?", $id)) return false;
        return true;
    }

    /**
     * @param int[] $ids
     * @return string[]
     */
    public static function getLoginsById(array $ids): array {
        $db = getDB();
        $logins = [];
        $q = $db->query("SELECT id, login FROM admins WHERE id IN (".implode(',', $ids).")");
        while ($row = $db->fetch($q)) {
            $logins[(int)$row['id']] = $row['login'];
        }
        return $logins;
    }

    protected static function getIdByLogin(string $login): ?int {
        $db = getDB();
        $q = $db->query("SELECT id FROM admins WHERE login=?", $login);
        return $db->numRows($q) > 0 ? (int)$db->result($q) : null;
    }

    public static function setPassword(string $login, string $password): bool {
        $db = getDB();
        $db->query("UPDATE admins SET password=? WHERE login=?", saltPassword($password), $login);
        return $db->affectedRows() > 0;
    }

    public static function auth(string $login, string $password): bool {
        $db = getDB();
        $salted_password = saltPassword($password);
        $q = $db->query("SELECT id, active FROM admins WHERE login=? AND password=?", $login, $salted_password);
        if (!$db->numRows($q)) {
            logDebug(__METHOD__.': login or password is invalid');
            return false;
        }

        $row = $db->fetch($q);
        $id = (int)$row['id'];
        $active = (bool)$row['active'];
        if (!$active)
            return false;

        $time = time();

        do {
            $token = strgen(32);
        } while ($db->numRows($db->query("SELECT id FROM admin_auth WHERE token=? LIMIT 1", $token)) > 0);

        $db->insert('admin_auth', [
            'admin_id' => $id,
            'token' => $token,
            'ts' => $time
        ]);
        $auth_id = $db->insertId();

        $db->insert('admin_log', [
            'admin_id' => $id,
            'ts' => $time,
            'ip' => ip2ulong($_SERVER['REMOTE_ADDR']),
            'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
        ]);

        $db->query("UPDATE admins SET activity_ts=? WHERE id=?", $time, $id);

        self::setSessionData($id, $login, $auth_id, $salted_password);
        self::setCookie($token);

        return true;
    }

    public static function logout() {
        if (!isAdmin())
            return;

        $db = getDB();
        $db->query("DELETE FROM admin_auth WHERE id=?", self::$authId);

        self::unsetSessionData();
        self::unsetCookie();
    }

    public static function logAction(\app\AdminActions\BaseAction $action) {
        \app\AdminActions\Util\Logger::record($action);
    }

    public static function check(): void {
        if (!isset($_COOKIE[self::ADMIN_COOKIE_NAME]))
            return;

        $cookie = (string)$_COOKIE[self::ADMIN_COOKIE_NAME];
        $db = getDB();
        $time = time();
        $q = $db->query("SELECT
            admin_auth.id AS auth_id,
            admin_auth.admin_id AS id,
            admins.activity_ts AS activity_ts,
            admins.password AS salted_password,
            admins.login AS login
        FROM admin_auth
        LEFT JOIN admins ON admin_auth.admin_id=admins.id
        WHERE admin_auth.token=?
        LIMIT 1", $cookie);

        if (!$db->numRows($q)) {
            unset($_COOKIE[self::ADMIN_COOKIE_NAME]);
            return;
        }

        $info = $db->fetch($q);
        self::setSessionData((int)$info['id'], $info['login'], (int)$info['auth_id'], $info['salted_password']);

        if ($time - $info['activity_ts'] > 15)
            $db->query("UPDATE admins SET activity_ts=? WHERE id=?", $time, self::$id);
    }

    protected static function setCookie(string $token): void {
        global $config;
        setcookie(self::ADMIN_COOKIE_NAME, $token, time() + self::ADMIN_SESSION_TIMEOUT, '/', $config['cookie_host']);
    }

    protected static function unsetCookie(): void {
        global $config;
        setcookie(self::ADMIN_COOKIE_NAME, '', 1, '/', $config['cookie_host']);
    }

    protected static function setSessionData(int $id, string $login, int $authId, string $saltedPassword) {
        self::$id = $id;
        self::$login = $login;
        self::$authId = $authId;
        self::$csrfSalt = saltPassword(strrev($saltedPassword));
    }

    protected static function unsetSessionData(): void {
        self::$id = null;
        self::$authId = null;
        self::$csrfSalt = null;
        self::$login = null;
    }

    public static function getId(): ?int {
        return self::$id;
    }

    public static function getCSRFSalt(): ?string {
        return self::$csrfSalt;
    }

    public static function getLogin(): ?string {
        return self::$login;
    }
}
