<?php

abstract class BaseHostGroupsModel
{
    protected $dbConnection;
    protected $cfdbConnection;
    protected $username;
    public static $cacheFileNameSuffix = '_groups_hosts_count.json' ;
    public const NAME_INT_SORT_COLUMN = 'nameIntSort';

    public function __construct($username)
    {
        $this->dbConnection = CfSettings::getInstance()->getConnection();
        $this->cfdbConnection = CfdbPdo::getInstance()->getConnection();
        $this->username = $username;
    }

    protected function getTotalGroupsCount($where, $bind)
    {
        $stmt = $this
            ->dbConnection
            ->prepare("SELECT count(*) as count FROM {$this->table} $where");
        $stmt->execute($bind);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result['count'];
    }

    public function list(array $args, array $where = [], array $whereBindData = []): bool|array
    {
        $offset = intval($args['skip'] ?? 0);
        $limit = intval($args['limit'] ?? 10);
        $sortColumn = null;
        $includeHostsCount = isset($args['includeHostsCount']) && $args['includeHostsCount'] == 'yes';

        // if sort column is nameIntSort, then sort by integers inside the name. this is needed to get next new group name number
        if (array_key_exists('sortColumn', $args)) {
            $sortColumn = $args['sortColumn'];
            if ($args['sortColumn'] === self::NAME_INT_SORT_COLUMN) {
                $sortColumn = "CASE
                                 WHEN regexp_replace(name, '\D', '', 'g') = '' THEN 0
                                 ELSE regexp_replace(name, '\D', '', 'g')::int
                               END";
            }
        }

        $orderBy = 'ORDER BY ' . ($sortColumn ?? $this->defaultSortColumn);
        $orderDirection = isset($args['sortDescending']) ?
            (($args['sortDescending'] == 'true') ? 'DESC' : 'ASC') :
            $this->defaultSortDirection;

        $bindData = ['limit' => $limit, 'offset' => $offset, 'username' => $args['username']];

        if (isset($args['searchQuery']) && !empty($args['searchQuery'])) {
            $whereBindData['searchQuery'] = trim($args['searchQuery']);
            $where[] = " name LIKE '%' || :searchQuery || '%' ";
        }

        $where = sizeof($where) > 0 ? 'WHERE ' . implode(' AND ', $where) : '';
        $statement = $this->dbConnection->prepare(
            "SELECT *,   
            CASE 
              WHEN (data_value->'classes')::text <> '{}'::text OR (data_value->'variables')::text <> '{}'::text THEN true
              ELSE false
            END AS has_data 
            FROM(
            SELECT *, shgd.value as data_value,
             (favorite_groups.group_id IS NOT NULL) as is_favorite, '" . $this::$type . "' as type FROM
                   {$this->table} 
                   LEFT JOIN favorite_groups ON favorite_groups.group_id = {$this->table}.id AND username = :username
                   LEFT JOIN shared_host_groups_data AS shgd ON shgd.group_id = id
                   ) shared_groups $where $orderBy $orderDirection LIMIT :limit OFFSET :offset"
        );

        $statement->execute(array_merge($bindData, $whereBindData));
        $groups = $statement->fetchAll(PDO::FETCH_ASSOC);

        $groups = array_map(function ($group) {
            $group['filter'] = json_decode($group['filter'], true);
            return $group;
        }, $groups);

        if ($includeHostsCount) {
            $hostsCountCacheTime = self::addHostsCountToTheResult($groups, $this->username, $this->cfdbConnection);
        }

        return [
            'data' => $groups,
            'meta' => [
                'count' => sizeof($groups),
                'page' => floor($offset / $limit) + 1,
                'timestamp' => time(),
                'total' => $this->getTotalGroupsCount($where, $whereBindData),
                'hostsCountCacheTime' => $hostsCountCacheTime ?? null
            ]
        ];
    }

    public static function addHostsCountToTheResult(array &$groups, string $username, PDO $cfdbConnection): int|null
    {
        // we use a file-based cache here to avoid the high frequency of the group hosts recounting
        // cache will last within 1 minute
        $cache = new CfCache($username . self::$cacheFileNameSuffix);
        $cacheTime = null;
        $updateCache = false;
        foreach ($groups as $key => $group) {
            if (!($count = $cache->getValueByKey($group['id']))) {
                $count = self::getHostsCountByGroup($group, $username, $cfdbConnection);
                $cache->setValueByKey($group['id'], $count);
                $updateCache = true;
            } else {
                $cacheTime = $cache->getTime();
            }
            $groups[$key]['hosts_number'] = $count;
        }
        $updateCache && $cache->saveCache();
        return $cacheTime;
    }

    public static function updateHostCountCachedValue(array $group, string $username, PDO $cfdbConnection): void
    {
        $cache = new CfCache($username . self::$cacheFileNameSuffix);
        // update if the cache exists
        if ($cache->getValueByKey($group['id'])) {
            $count = self::getHostsCountByGroup($group, $username, $cfdbConnection);
            $cache->setValueByKey($group['id'], $count);
            $cache->saveCache();
        }
    }

    protected static function getHostsCountByGroup(array $group, string $username, PDO $cfdbConnection): int
    {
        if ($group['type'] == PersonalHostGroupsModel::$type) {
            $stmt = $cfdbConnection->prepare("SELECT count(*) FROM groups.get_hosts_in_personal_group(?, ?)");
            $stmt->execute([$group['id'], $username]);
            $count = $stmt->fetch(PDO::FETCH_ASSOC)['count'];
        } else {
            $stmt = $cfdbConnection->prepare("SELECT count(*) FROM groups.get_hosts_in_shared_group(?)");
            $stmt->execute([$group['id']]);
            $count = $stmt->fetch(PDO::FETCH_ASSOC)['count'];
        }

        return $count;
    }

    public function sendCmdbRefreshPsqlNotification(): int|bool
    {
        return $this->cfdbConnection->exec("SELECT pg_notify('cmdb_refresh', 'all')");
    }

    abstract public function get(int $id, bool $updateHostCountCache = true): array|bool;

    abstract public function create(HostGroupEntity $groupEntity);

    abstract public function update(int $id, HostGroupEntity $groupEntity);

    abstract public function delete(int $id);
}
