<?php

namespace Build\Models;

use Build\Entities\ModuleEntity;

class ModulesListModel
{
    private \PDO $dbConnection;
    private $table = 'build_modules';

    const RELEVANCE_SORT_NAME = 'relevance';

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

    public function update($indexUrl)
    {
        $buildIndexResponse = (new \Pest(''))->get($indexUrl, ['user_agent', USER_AGENT]);
        $modules = json_decode($buildIndexResponse, $associative = true);

        foreach ($modules as $name => $versions) {
            foreach ($versions as $version => $module) {
                $moduleEntity = ModuleEntity::fromArray($module);
                $moduleEntity->setLatest($module['versions'][$version]['latest']);
                if (!$moduleEntity->getLatest()) {
                    $moduleEntity->setName($name);
                }
                $this->insertOrUpdate($moduleEntity);
            }
        }
    }

    private function insertOrUpdate(ModuleEntity $moduleEntity)
    {
        $data = $moduleEntity->toArray();
        $columns = implode(',', array_keys($data));
        $keysToBind = ':' . implode(',:', array_keys($data));

        $updateOnConflictColumns = array_reduce(
            array_keys($data),
            function ($reducer, $column) {
                return $reducer . (strlen($reducer) > 0 ? ' ,' : '') . "$column = EXCLUDED.$column";
            },
            ""
        );
        $sql = "INSERT INTO {$this->table} ($columns) VALUES ($keysToBind) ON CONFLICT (name, version) DO UPDATE SET $updateOnConflictColumns ";
        $this->dbConnection->prepare($sql)->execute($data);
    }

    public function get(array $args): array
    {
        $offset = $args['skip'] ?? 0;
        $limit = $args['limit'] ?? 10;
        $orderBy =  'ORDER BY "' . ($args['sortColumn'] ?? 'name') . '"';
        $orderDirection = (isset($args['sortDescending']) && $args['sortDescending'] == 'true') ? 'DESC' : 'ASC';

        $where = 'WHERE latest IS TRUE ';
        $bindData = ['limit' => $limit, 'offset' => $offset];
        $whereData = [];

        if (isset($args['searchQuery']) && !empty($args['searchQuery'])) {
            $where .= "AND (name LIKE ('%' || :search || '%') OR description LIKE ('%' || :search || '%'))";
            $whereData['search'] = preg_replace('/\s+/', '%', trim($args['searchQuery']));

            if ($args['sortColumn'] == self::RELEVANCE_SORT_NAME) {
                // replace spaces in the search query with & operator
                $bindData['searchTsQuery'] = preg_replace('/\s+/', ':*&', trim($args['searchQuery']));
                $orderBy = 'ORDER BY ts_rank(ts_vector, to_tsquery(:searchTsQuery))';
            }
        }

        if (isset($args['tag'])) {
            $where .= strlen($where) > 0 ? ' AND' : 'WHERE';
            $where .= ' tags ?? :tag ';
            $whereData['tag'] = $args['tag'];
        }

        $stmt = $this->dbConnection->prepare(
            "SELECT * FROM {$this->table} $where $orderBy $orderDirection LIMIT :limit OFFSET :offset"
        );
        $stmt->execute(array_merge($bindData, $whereData));

        $modules = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        $modules = array_map(function ($module) {
            $module['versions'] = json_decode($module['versions'], $associative = true);
            $module['author'] = json_decode($module['author'], $associative = true);
            return $module;
        }, $modules);

        return [
            'data' => $modules,
            'meta' => [
                'count' => sizeof($modules),
                'page' => floor($offset / $limit) + 1,
                'timestamp' => time(),
                'total' => $this->getTotalModulesCount($where, $whereData)
            ]
        ];
    }

    public function getTotalModulesCount($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 getModulesByName(array $names): array
    {
        $params = rtrim(str_repeat('?,', count($names)), ',');
        $stmt = $this->dbConnection->prepare("SELECT * FROM {$this->table} WHERE name IN ($params) AND latest IS TRUE");
        $stmt->execute($names);
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }
}
