<?php

namespace KupShop\CatalogBundle\Parameters;

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Query\Operator;

class ParameterFinder
{
    use \DatabaseCommunication;

    protected $listParameter;
    protected $listParameterValues;
    protected $parameterInstancesCache = [];

    protected function loadParametersCache()
    {
        if (is_null($this->listParameter)) {
            $this->listParameter = sqlFetchAll(sqlQuery('SELECT par.*, LOWER(name) as name FROM parameters par'), 'id');
        }
    }

    /**
     * @return \Parameter
     */
    public function getParameter($id_parameter)
    {
        $this->loadParametersCache();

        if (!($this->parameterInstancesCache[$id_parameter] ?? false)) {
            $parameter = new \Parameter();
            $parameter->createFromArray($this->listParameter[$id_parameter]);

            $this->parameterInstancesCache[$id_parameter] = $parameter;
        }

        return $this->parameterInstancesCache[$id_parameter];
    }

    public function getParameterById(int $parameterId): ?\Parameter
    {
        $this->loadParametersCache();

        if (!($this->listParameter[$parameterId] ?? false)) {
            return null;
        }

        if (!($this->parameterInstancesCache[$parameterId] ?? false)) {
            $parameter = new \Parameter();
            $parameter->createFromArray($this->listParameter[$parameterId]);

            $this->parameterInstancesCache[$parameterId] = $parameter;
        }

        return $this->parameterInstancesCache[$parameterId];
    }

    public function getParameterId(string $name): ?int
    {
        $this->loadParametersCache();

        $nameLower = mb_strtolower($name);
        foreach ($this->listParameter as $id => $parameter) {
            if ($parameter['name'] == $nameLower) {
                return (int) $id;
            }
        }

        return null;
    }

    public function findParameter($name, $type = 'list')
    {
        if ($name == '') {
            return null;
        }

        $this->loadParametersCache();

        if ($parameterId = $this->getParameterId($name)) {
            return $parameterId;
        }

        $nameLower = mb_strtolower($name);

        $max_position = returnSQLResult('SELECT MAX(position) FROM parameters');
        try {
            $index = sqlGetConnection()->transactional(function () use ($name, $max_position, $type) {
                $this->insertSQL('parameters', ['name' => $name, 'position' => $max_position + 1, 'value_type' => $type]);

                return sqlInsertId();
            });
        } catch (UniqueConstraintViolationException $e) {
            $index = $this->selectSQL('parameters', ['name' => $name])->fetch()['id'];
        }

        $this->listParameter[$index] = ['id' => $index, 'name' => $nameLower, 'position' => $max_position + 1, 'value_type' => $type];

        return $index;
    }

    public function getParameterValueId(int $parameterId, string $value): ?int
    {
        if ($index = array_search(mb_strtolower($value), $this->getListParametersValuesCache($parameterId))) {
            return $index;
        }

        return null;
    }

    public function findParameterValue($parameter_id, $value)
    {
        if ($value == '') {
            return null;
        }

        $params = $this->getListParametersValuesCache($parameter_id);

        if ($index = array_search(mb_strtolower($value), $params)) {
            return $index;
        }

        try {
            $index = sqlGetConnection()->transactional(function () use ($value, $parameter_id) {
                $this->insertSQL('parameters_list', ['value' => $value, 'id_parameter' => $parameter_id]);

                return sqlInsertId();
            });
        } catch (\Exception $e) {
            $index = sqlFetchAssoc($this->selectSQL('parameters_list', ['value' => $value, 'id_parameter' => $parameter_id], ['id']))['id'];
        }

        $this->listParameterValues[$parameter_id][$index] = mb_strtolower($value);

        return $index;
    }

    private function getListParametersValuesCache(int $parameterId): array
    {
        if (!isset($this->listParameterValues[$parameterId])) {
            $values = sqlQuery('SELECT id, LOWER(value) as value
                      FROM parameters_list WHERE id_parameter=:id_parameter',
                ['id_parameter' => $parameterId]);
            $this->listParameterValues[$parameterId] = sqlFetchAll($values, ['id' => 'value']);
        }

        return $this->listParameterValues[$parameterId] ?? [];
    }

    /**
     * @param string $parameter Parameter name
     * @param string $value     Parameter value
     * @param string $type      Parameter type
     *
     * @return array array with keys "id_parameter" and "value"
     */
    public function findParameterAndValue($parameter, $value, $type = 'list')
    {
        $id_parameter = $this->findParameter($parameter, $type);

        if ($type == 'list') {
            $value = $this->findParameterValue($id_parameter, $value);
        }

        return ['id_parameter' => $id_parameter, 'value' => $value];
    }

    /** Replace all product parameter values with new one.
     */
    public function setProductParameters($id_product, $id_parameter, $values)
    {
        $parameter = $this->getParameter($id_parameter);

        $data = ['id_parameter' => $id_parameter, 'id_product' => $id_product];
        $this->deleteSQL('parameters_products', $data);

        foreach ($values as $value) {
            if (empty($value)) {
                continue;
            }
            $data['value'] = $value;
            $parameter->setValue($data);
        }
    }

    /**
     * Narozdil od `setProductParameters` neprovadi pokazde delete a insert, takze je mene narocna pro
     * databazi a negeneruje tak velke mnozstvi dat do binlogu.
     */
    public function updateProductParameters(int $productId, int $parameterId, array $values): void
    {
        if (!($parameter = $this->getParameterById($parameterId))) {
            return;
        }

        $valueField = "value_{$parameter->value_type}";

        // nactu si aktualni hodnoty parametru od produktu
        $oldValues = sqlQueryBuilder()
            ->select('id', $valueField)
            ->from('parameters_products')
            ->where(Operator::equals(['id_parameter' => $parameterId, 'id_product' => $productId]))
            ->orderBy('id', 'ASC')
            ->execute()->fetchAllAssociative();

        $oldValuesOnly = array_map(fn ($x) => $x[$valueField], $oldValues);

        // najdu si hodnoty, ktere mam smazat
        $deleteDiff = array_diff_assoc($oldValuesOnly, $values);
        // najdu si hodnoty, ktere mam insertovat
        $insertDiff = array_diff_assoc($values, $oldValuesOnly);

        $deleteIds = [];
        // chci to mazat podle ID, takze dohledam ID
        foreach ($deleteDiff as $position => $value) {
            $deleteIds[] = $oldValues[$position]['id'];
        }

        // provedu delete
        if (!empty($deleteIds)) {
            sqlQueryBuilder()
                ->delete('parameters_products')
                ->andWhere(Operator::inIntArray($deleteIds, 'id'))
                ->execute();
        }

        // provedu insert
        foreach ($insertDiff as $value) {
            sqlQueryBuilder()
                ->insert('parameters_products')
                ->directValues(
                    [
                        'id_parameter' => $parameterId,
                        'id_product' => $productId,
                        $valueField => $value,
                    ]
                )->execute();
        }
    }
}
