<?php

class SharedGroupCmdbModel
{
    private \PDO $dbConnection;
    private string $table = 'shared_host_groups_data';
    private string $username;


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


    public function createOrUpdate(SharedGroupCmdbItemEntity $entity): void
    {
        $sql = "INSERT INTO {$this->table} (group_id, value, epoch) 
                VALUES (:groupId, :value, :epoch) 
                ON CONFLICT (group_id) 
                DO UPDATE SET 
                value = EXCLUDED.value, epoch = EXCLUDED.epoch, updated_at = 'now()'";

        $this->dbConnection->prepare($sql)->execute([
            'groupId' => $entity->getGroupId(),
            'value' => $entity->valuesToJson(),
            'epoch' => $this->maxEpoch() + 1,
        ]);
    }

    public function maxEpoch()
    {
        $sql = "SELECT max(epoch) FROM {$this->table}";
        $result = $this->dbConnection->query($sql)->fetch(\PDO::FETCH_ASSOC);
        return $result['max'] ?? 0;
    }

    public function update(SharedGroupCmdbItemEntity $entity): void
    {
        $sql = "UPDATE {$this->table} SET value = :value, epoch = :epoch, updated_at = 'now()' WHERE group_id = :groupId";
        $this->dbConnection->prepare($sql)->execute([
            'value' => $entity->valuesToJson(),
            'epoch' => $this->maxEpoch() + 1,
            'groupId' => $entity->getGroupId()
        ]);
    }

    public function updateByName(int $groupId, string $type, string $name, array $requestData, array $existingData): void
    {
        // get name from requested data if exists to update var or class name
        $newName = isset($requestData['name']) && $requestData['name'] !== $name ? $requestData['name'] : null;
        unset($requestData['name']);
        $mergedData = array_merge((array)$existingData[$type][$name], $requestData);

        $params = [
            'value' => json_encode($mergedData),
            'epoch' => $this->maxEpoch() + 1,
            'groupId' => $groupId
        ];

        if ($newName !== null) {
            $sql = "UPDATE {$this->table} 
                    SET value = jsonb_set(value #- :oldPath, :newPath, :value), epoch = :epoch, updated_at = 'now()' 
                    WHERE group_id = :groupId";
            $params['oldPath'] = '{' . "$type,$name" . '}';
            $params['newPath'] = '{' . "$type,$newName" . '}';
        } else {
            $sql = "UPDATE {$this->table} 
                    SET value = jsonb_set(value, :path, :value), epoch = :epoch, updated_at = 'now()' 
                    WHERE group_id = :groupId";
            $params['path'] = '{' . "$type,$name" . '}';
        }

        $this->dbConnection->prepare($sql)->execute($params);
    }

    public function insertOrUpdateByName(int $groupId, string $type, string $name, array $requestData): void
    {
        $newValue = ['classes' => new \stdClass(), 'variables' => new \stdClass()];
        $newValue[$type] = [$name => $requestData];

        $sql = "INSERT INTO {$this->table} 
                (group_id, value, epoch, updated_at) 
                VALUES (:groupId,  :newValue, :epoch, 'now()') 
                ON CONFLICT (group_id) 
                DO UPDATE SET epoch = EXCLUDED.epoch, updated_at = EXCLUDED.updated_at, value = jsonb_set(
                    jsonb_build_object(
                        'classes', 
                        coalesce({$this->table}.value->>'classes','{}')::jsonb, 
                        'variables', 
                        coalesce({$this->table}.value->>'variables','{}')::jsonb
                    ), 
                    :path, 
                    :value, 
                    TRUE
                    )";

        $this->dbConnection->prepare($sql)->execute([
            'value' => empty($requestData) ? '{}' : json_encode($requestData),
            'path' => '{' . "$type, $name" . '}',
            'epoch' => $this->maxEpoch() + 1,
            'newValue' => json_encode($newValue),
            'groupId' => $groupId
        ]);
    }

    public function getByName(int $groupId, string $type, string $name): ?array
    {
        $data = null;
        $query = "SELECT value->:type->:name AS data 
                  FROM {$this->table} 
                  WHERE group_id = :groupId AND (value->:type->:name) is not null";
        $stmt = $this->dbConnection->prepare($query);
        $stmt->execute([
            'groupId' => $groupId,
            'type' => $type,
            'name' => $name,
        ]);

        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        if (isset($result['data']) && $result['data'] != null) {
            $data = ['groupId' => $groupId];
            $data[$type][$name] = json_decode($result['data']);
        }

        return $data;
    }


    public function deleteByName(int $groupId, string $type, string $name): void
    {
        $sql = "UPDATE {$this->table} SET value = value #- :path, epoch = :epoch, updated_at = 'now()' WHERE group_id = :groupId";
        $this->dbConnection->prepare($sql)->execute([
            'path' => '{' . "$type,$name" . '}',
            'epoch' => $this->maxEpoch() + 1,
            'groupId' => $groupId,
        ]);
    }

    public function get(int $groupId): null|array
    {
        $query = "SELECT group_id, value FROM {$this->table} WHERE group_id = :groupId";

        $stmt = $this->dbConnection->prepare($query);
        $stmt->execute(['groupId' => $groupId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);

        return (isset($result['value']) && $result['value'] != null) ?
            [
                'group_id' => $result['group_id'],
                'value' => json_decode($result['value'])
            ] :
            null;
    }

    public function delete(int $groupId): void
    {
        $this
            ->dbConnection
            ->prepare("DELETE FROM {$this->table}  WHERE group_id = :groupId")
            ->execute(['groupId' => $groupId]);
    }
}
