<?php

class SharedHostGroupsModel extends BaseHostGroupsModel
{
    protected $table = 'shared_host_groups';
    public static $type = 'shared';

    protected string $defaultSortColumn = 'priority';
    protected string $defaultSortDirection = 'ASC';

    public function get(int $id, bool $updateHostCountCache = true): array|bool
    {
        $stmt = $this->dbConnection->prepare("SELECT *, '" . static::$type . "' as type FROM {$this->table} WHERE id = ? AND deletion_time IS NULL");
        $stmt->execute([$id]);
        $group = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$group) {
            return false;
        }

        if (isset($group['filter'])) {
            $group['filter'] = json_decode($group['filter'], true);
        }

        if ($updateHostCountCache) {
            // update a group hosts count when a single group request performed
            self::updateHostCountCachedValue($group, $this->username, $this->cfdbConnection);
        }

        return $group;
    }

    public function create(HostGroupEntity $groupEntity): bool|string
    {
        $groupData = $groupEntity->toArray();
        if ($groupEntity->getPriority() === null) {
            unset($groupData['priority']);
            $sql = "INSERT INTO {$this->table} 
                (creator, name, description, filter, filter_sql) 
                VALUES (:creator, :name, :description, :filter, :filter_sql)
                RETURNING id";

            $stmt = $this->dbConnection->prepare($sql);
            $stmt->execute($groupData);
        } else {
            $this->dbConnection->beginTransaction();
            try {
                /** free priority place to insert there new group, otherwise will be conflict.
                 * It increments next priorities to that we are going to insert.
                 * Adjustment happens from the last priority (ORDER BY priority DESC) otherwise we will take
                 * already occupied priority.
                 */
                $stmt = $this->dbConnection->prepare("CALL increment_priorities_starting_from(?)");
                $stmt->execute([$groupEntity->getPriority()]);

                $sql = "INSERT INTO {$this->table} 
                    (creator, name, description, filter, filter_sql, priority) 
                    VALUES (:creator, :name, :description, :filter, :filter_sql, :priority)
                    RETURNING id";
                $stmt = $this->dbConnection->prepare($sql);
                $stmt->execute($groupData);
            } catch (Exception $exception) {
                $this->dbConnection->rollBack();
                throw $exception;
            }
            $this->dbConnection->commit();
        }
        $this->dbConnection->exec("CALL resequence_shared_hosts_groups_priorities()");
        $insertedRow = $stmt->fetch(PDO::FETCH_ASSOC);
        return $insertedRow['id'];
    }

    public function update(int $id, HostGroupEntity $groupEntity): void
    {
        $group = $this->get($id);
        $isPriorityChanged = ($groupEntity->getPriority() !== null) && $groupEntity->getPriority() !== $group['priority'];

        if ($isPriorityChanged && $groupEntity->getPriority() > $group['priority']) {
            // when new priority more than existing then we need to increment it
            // otherwise after reindexing new priority will be not what expected
            $groupEntity->setPriority($groupEntity->getPriority() + 1);
        } elseif ($isPriorityChanged && $groupEntity->getPriority() == -1) {
            // if priority equals minus one then move it to the bottom of the list
            $groupsCount = $this->getTotalGroupsCount('WHERE deletion_time IS NULL', []);
            $groupEntity->setPriority($groupsCount + 1);
        }

        $set = [];
        $data = $groupEntity->changedItemsToArray();
        unset($data['creator']); // creator cannot be changed

        if (sizeof($data) === 0) {
            return;
        }

        foreach ($data as $key => $value) {
            $set[] = "$key = :$key";
        }

        $data['id'] = $id;
        $updateSql = "UPDATE {$this->table} SET " . implode(', ', $set) . " WHERE id = :id";
        $this->dbConnection->beginTransaction();
        try {
            if ($isPriorityChanged) {
                $stmt = $this->dbConnection->prepare("CALL increment_priorities_starting_from(?)");
                $stmt->execute([$groupEntity->getPriority()]);
            }
            $this->dbConnection->prepare($updateSql)->execute($data);
            $this->dbConnection->exec("CALL resequence_shared_hosts_groups_priorities()");
        } catch (Exception $exception) {
            $this->dbConnection->rollBack();
            throw $exception;
        }
        $this->dbConnection->commit();
    }

    public function delete(int $id)
    {
        $result = $this
            ->dbConnection
            ->prepare("UPDATE {$this->table} SET deletion_time = now() WHERE id = ?")
            ->execute([$id]);
        $this->dbConnection->exec("CALL resequence_shared_hosts_groups_priorities()");
        return $result;
    }

    public function getByHostkey(string $hostkey)
    {
        $stmt = $this->cfdbConnection->prepare("WITH host_groups AS (SELECT * FROM groups.get_shared_groups_of_host(?)) SELECT hg.id, name FROM groups.shared_host_groups as shg JOIN host_groups hg ON hg.id = shg.id ORDER BY shg.priority");
        $stmt->execute([$hostkey]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function makePersonal(
        $id,
        PersonalHostGroupEntity $personalHostGroupEntity,
        PersonalHostGroupsModel $personalHostGroupsModel
    ) {
        $this->dbConnection->beginTransaction();
        try {
            $personalGroupId = $personalHostGroupsModel->create($personalHostGroupEntity);
            $this->delete($id);
        } catch (Exception $exception) {
            $this->dbConnection->rollBack();
            throw $exception;
        }
        $this->dbConnection->commit();
        return $personalGroupId;
    }

    public function isNameUnique($name, $id = null): bool
    {
        $stmt = $this->dbConnection->prepare("SELECT id FROM {$this->table} WHERE name = ? AND deletion_time IS NULL");
        $stmt->execute([$name]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);

        return ($id === null) ? $stmt->rowCount() === 0 : $stmt->rowCount() === 0 || $result['id'] === $id;
    }

}
