<?php
namespace Misuzu\Users;

use InvalidArgumentException;
use RuntimeException;
use Index\Colour\Colour;
use Index\Data\DbStatementCache;
use Index\Data\DbTools;
use Index\Data\IDbConnection;
use Misuzu\Pagination;

class Roles {
    public const DEFAULT_ROLE = '1';

    private IDbConnection $dbConn;
    private DbStatementCache $cache;

    public function __construct(IDbConnection $dbConn) {
        $this->dbConn = $dbConn;
        $this->cache = new DbStatementCache($dbConn);
    }

    public function countRoles(
        UserInfo|string|null $userInfo = null,
        ?bool $hidden = null
    ): int {
        if($userInfo instanceof UserInfo)
            $userInfo = $userInfo->getId();

        $hasUserInfo = $userInfo !== null;
        $hasHidden = $hidden !== null;

        $args = 0;
        $query = 'SELECT COUNT(*) FROM msz_roles';
        if($hasUserInfo) {
            ++$args;
            $query .= ' WHERE role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)';
        }
        if($hasHidden)
            $query .= sprintf(' %s role_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '=' : '<>');

        $args = 0;
        $stmt = $this->cache->get($query);
        if($hasUserInfo)
            $stmt->addParameter(++$args, $userInfo);
        $stmt->execute();

        $count = 0;
        $result = $stmt->getResult();

        if($result->next())
            $count = $result->getInteger(0);

        return $count;
    }

    public function getRoles(
        UserInfo|string|null $userInfo = null,
        ?bool $hidden = null,
        ?Pagination $pagination = null
    ): array {
        if($userInfo instanceof UserInfo)
            $userInfo = $userInfo->getId();

        $hasUserInfo = $userInfo !== null;
        $hasHidden = $hidden !== null;
        $hasPagination = $pagination !== null;

        $args = 0;
        $query = 'SELECT role_id, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour, UNIX_TIMESTAMP(role_created) FROM msz_roles';
        if($hasUserInfo) {
            ++$args;
            $query .= ' WHERE role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)';
        }
        if($hasHidden)
            $query .= sprintf(' %s role_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '<>' : '=');
        if($hasPagination)
            $query .= ' LIMIT ? OFFSET ?';

        $args = 0;
        $stmt = $this->cache->get($query);
        if($hasUserInfo)
            $stmt->addParameter(++$args, $userInfo);
        if($hasPagination) {
            $stmt->addParameter(++$args, $pagination->getRange());
            $stmt->addParameter(++$args, $pagination->getOffset());
        }
        $stmt->execute();

        $roles = [];
        $result = $stmt->getResult();

        while($result->next())
            $roles[] = new RoleInfo($result);

        return $roles;
    }

    public function getRole(string $roleId): RoleInfo {
        $stmt = $this->cache->get('SELECT role_id, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour, UNIX_TIMESTAMP(role_created) FROM msz_roles WHERE role_id = ?');
        $stmt->addParameter(1, $roleId);
        $stmt->execute();

        $result = $stmt->getResult();
        if(!$result->next())
            throw new RuntimeException('Could not find role with ID $roleId.');

        return new RoleInfo($result);
    }

    public function createRole(
        string $name,
        int $rank,
        Colour $colour,
        string $title = '',
        string $description = '',
        bool $hidden = false,
        bool $leavable = false
    ): RoleInfo {
        $colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour);

        // should these continue to accept NULL?
        if($title === '') $title = null;
        if($description === '') $description = null;

        $stmt = $this->cache->get('INSERT INTO msz_roles (role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour) VALUES (?, ?, ?, ?, ?, ?, ?)');
        $stmt->addParameter(1, $rank);
        $stmt->addParameter(2, $name);
        $stmt->addParameter(3, $title);
        $stmt->addParameter(4, $description);
        $stmt->addParameter(5, $hidden ? 1 : 0);
        $stmt->addParameter(6, $leavable ? 1 : 0);
        $stmt->addParameter(7, $colour);
        $stmt->execute();

        return $this->getRole((string)$this->dbConn->getLastInsertId());
    }

    public function deleteRoles(RoleInfo|string|array $roleInfos): void {
        if(!is_array($roleInfos))
            $roleInfos = [$roleInfos];
        elseif(empty($roleInfos))
            return;

        $stmt = $this->cache->get(sprintf(
            'DELETE FROM msz_roles WHERE role_id IN (%s)',
            DbTools::prepareListString($roleInfos)
        ));

        $args = 0;
        foreach($roleInfos as $roleInfo) {
            if($roleInfo instanceof RoleInfo)
                $roleInfo = $roleInfo->getId();
            elseif(!is_string($roleInfo))
                throw new InvalidArgumentException('$roleInfos must be strings of instances of RoleInfo.');

            $stmt->addParameter(++$args, $roleInfo);
        }

        $stmt->execute();
    }

    public function updateRole(
        RoleInfo|string $roleInfo,
        ?string $name = null,
        ?int $rank = null,
        ?Colour $colour = null,
        ?string $title = null,
        ?string $description = null,
        ?bool $hidden = null,
        ?bool $leavable = null
    ): void {
        if($roleInfo instanceof RoleInfo)
            $roleInfo = $roleInfo->getId();

        $applyTitle = $title !== null;
        $applyDescription = $description !== null;
        $applyColour = $colour !== null;
        $applyHidden = $hidden !== null;
        $applyLeavable = $leavable !== null;

        if($applyColour)
            $colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour);

        // should these continue to accept NULL?
        if($title === '') $title = null;
        if($description === '') $description = null;

        $stmt = $this->cache->get('UPDATE msz_roles SET role_hierarchy = COALESCE(?, role_hierarchy), role_name = COALESCE(?, role_name), role_title = IF(?, ?, role_title), role_description = IF(?, ?, role_description), role_hidden = IF(?, ?, role_hidden), role_can_leave = IF(?, ?, role_can_leave), role_colour = IF(?, ?, role_colour) WHERE role_id = ?');
        $stmt->addParameter(1, $rank);
        $stmt->addParameter(2, $name);
        $stmt->addParameter(3, $applyTitle ? 1 : 0);
        $stmt->addParameter(4, $title);
        $stmt->addParameter(5, $applyDescription ? 1 : 0);
        $stmt->addParameter(6, $description);
        $stmt->addParameter(7, $applyHidden ? 1 : 0);
        $stmt->addParameter(8, $hidden ? 1 : 0);
        $stmt->addParameter(9, $applyLeavable ? 1 : 0);
        $stmt->addParameter(10, $leavable ? 1 : 0);
        $stmt->addParameter(11, $applyColour ? 1 : 0);
        $stmt->addParameter(12, $colour);
        $stmt->addParameter(13, $roleInfo);
        $stmt->execute();
    }

    public function getDefaultRole(): RoleInfo {
        return $this->getRole(self::DEFAULT_ROLE);
    }

    public function countRoleUsers(RoleInfo|string $roleInfo): int {
        if($roleInfo instanceof RoleInfo)
            $roleInfo = $roleInfo->getId();

        $stmt = $this->cache->get('SELECT COUNT(*) FROM msz_users_roles WHERE role_id = ?');
        $stmt->addParameter(1, $roleInfo);
        $stmt->execute();

        $count = 0;
        $result = $stmt->getResult();

        if($result->next())
            $count = $result->getInteger(0);

        return $count;
    }
}
