<?php

class SharedGroupCmdbModel
{
    private \PDO $dbConnection;
    private string $table = 'shared_host_groups_data';
    private string $entriesTable = 'shared_host_groups_data_entries';
    private string $subEntriesTable = 'shared_host_groups_data_subentries';
    private string $username;

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

    public function create(SharedGroupCmdbEntry $cmdbEntry): int
    {
        try {
            $this->dbConnection->beginTransaction();
            $sql = "INSERT INTO {$this->entriesTable} (group_id, name, description, tags, type, meta) 
                VALUES (:group_id, :name, :description, :tags, :type, :meta)";

            $this->dbConnection->prepare(query: $sql)->execute($cmdbEntry->toArray());

            $entryId = $this->dbConnection->lastInsertId();

            foreach ($cmdbEntry->subentries as $cmdbHostSubEntry) {
                $cmdbHostSubEntry->setEntryId($entryId);
                $subentriesSql = "INSERT INTO {$this->subEntriesTable} (group_id, entry_id, item_name, item_type, item_value) 
                VALUES (:group_id, :entry_id, :item_name, :item_type, :item_value)";

                $this->dbConnection->prepare($subentriesSql)->execute($cmdbHostSubEntry->toArray());
            }

            $this->dbConnection->commit();
            return $entryId;
        } catch (\Exception $e) {
            $this->dbConnection->rollBack();
            syslog(LOG_ERR, "Failed to create CMDB group entry: " . $e->getMessage());
            throw $e;
        }
    }

    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(int $id, SharedGroupCmdbEntry $cmdbEntry)
    {
        try {
            $this->dbConnection->beginTransaction();
            $sql = "UPDATE {$this->entriesTable} SET name = :name, description = :description, tags = :tags, meta = :meta WHERE id = :id";
            $filteredValues = array_intersect_key($cmdbEntry->toArray(), array_flip(['name', 'description', 'tags', 'meta']));

            $this->dbConnection->prepare(query: $sql)->execute([...$filteredValues, 'id' => $id]);
            $this->dbConnection->prepare("DELETE FROM {$this->subEntriesTable} WHERE entry_id = :entry_id")->execute(['entry_id' => $id]);
            foreach ($cmdbEntry->subentries as $cmdbSubEntry) {
                $cmdbSubEntry->setEntryId($id);
                $subentriesSql = "INSERT INTO {$this->subEntriesTable} (group_id, entry_id, item_name, item_type, item_value) 
                VALUES (:group_id, :entry_id, :item_name, :item_type, :item_value)";
                $this->dbConnection->prepare($subentriesSql)->execute($cmdbSubEntry->toArray());
            }
            $this->dbConnection->commit();
        } catch (\Exception $e) {
            $this->dbConnection->rollBack();
            syslog(LOG_ERR, "Failed to update CMDB group entry: " . $e->getMessage());
            throw new $e();
        }
    }


    public function getSubEntry(string $type, string $name, string $id): mixed
    {
        $query = "SELECT * 
                  FROM {$this->subEntriesTable}
                  WHERE item_type = :type
                  AND item_name = :name
                  AND group_id = :groupId";

        $stmt = $this->dbConnection->prepare($query);
        $stmt->execute(['groupId' => $id, 'name' => $name, 'type' => $type]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    public function getById(string $entryId): mixed
    {
        $query = "SELECT * FROM {$this->entriesTable} WHERE id = :id";
        $stmt = $this->dbConnection->prepare($query);
        $stmt->execute(['id' => $entryId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    public function list(int $groupId, QueryApiParamsDto $params): null|array
    {
        $query = "SELECT {$this->entriesTable}.*, json_agg({$this->subEntriesTable}) AS entries, to_json({$this->entriesTable}.tags) AS tags
                  FROM {$this->entriesTable}
                  LEFT JOIN {$this->subEntriesTable} ON {$this->subEntriesTable}.entry_id = {$this->entriesTable}.id
                  WHERE {$this->entriesTable}.group_id = :groupId
                  GROUP BY {$this->entriesTable}.id
                  ORDER BY created_at DESC
                  LIMIT :limit OFFSET :offset";



        $stmt = $this->dbConnection->prepare($query);
        $stmt->execute(['groupId' => $groupId, 'limit' => $params->limit, 'offset' => $params->skip]);
        $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

        $result = array_map(function ($item) {
            $item['entries'] = json_decode($item['entries'], false);
            $item['meta'] = json_decode($item['meta'], false);
            $item['tags'] = json_decode($item['tags'], false);
            return $item;
        }, $result);

        $totalQuery = "SELECT count(*) AS count FROM {$this->entriesTable} WHERE group_id = :groupId";
        $totalStmt = $this->dbConnection->prepare($totalQuery);
        $totalStmt->execute(['groupId' => $groupId]);
        $total = $totalStmt->fetchColumn();

        return [
            'data' => $result,
            'meta' => [
                'total' => $total,
                'page' => floor($params->skip / $params->limit) + 1,
                'count' => sizeof($result),
            ]
        ];
    }

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

    public function getPolicyConfigurationIds(string $id): array
    {
        $query = "SELECT meta->>'policyId' as policyid
                  FROM {$this->entriesTable}
                  WHERE type = 'policy_configuration'
                  AND group_id = :groupId";

        $stmt = $this->dbConnection->prepare($query);
        $stmt->execute(['groupId' => $id]);
        return array_column($stmt->fetchAll(PDO::FETCH_ASSOC), 'policyid');
    }
}
