<?php
namespace Incoviba\Repository\Venta;

use DateTimeInterface;
use DateInterval;
use PDO;
use Incoviba\Common;
use Incoviba\Model;
use Incoviba\Repository;

class Reservation extends Common\Ideal\Repository
{
    public function __construct(Common\Define\Connection $connection, protected Repository\Persona $personaRepository,
                                protected Repository\Proyecto\Broker $brokerRepository,
                                protected Unidad $unitRepository, protected Promotion $promotionRepository)
    {
        parent::__construct($connection);
    }

    public function getTable(): string
    {
        return 'reservations';
    }

    public function create(?array $data = null): Model\Venta\Reservation
    {
        $map = (new Common\Implement\Repository\MapperParser())
            ->register('buyer_rut', (new Common\Implement\Repository\Mapper())
                ->setProperty('buyer')
                ->setFunction(function($data) use ($data) {
                    return $this->personaRepository->fetchById($data['buyer_rut']);
                }))
            ->register('date', new Common\Implement\Repository\Mapper\DateTime('date'))
            ->register('broker_rut', (new Common\Implement\Repository\Mapper())
                ->setProperty('broker')
                ->setDefault(null)
                ->setFunction(function($data) use ($data) {
                    try {
                        return $this->brokerRepository->fetchById($data['broker_rut']);
                    } catch (Common\Implement\Exception\EmptyResult) {
                        return null;
                    }
                }));
        return $this->parseData(new Model\Venta\Reservation(), $data, $map);
    }
    public function save(Common\Define\Model $model): Model\Venta\Reservation
    {
        $model->id = $this->saveNew([
            'buyer_rut',
            'date',
            'broker_rut'
        ], [
            $model->buyer->rut,
            $model->date->format('Y-m-d'),
            $model->broker?->rut
        ]);
        $this->saveUnits($model);
        $this->savePromotions($model);
        return $model;
    }

    /**
     * @param Common\Define\Model $model
     * @param array $new_data
     * @return Model\Venta\Reservation
     * @throws Common\Implement\Exception\EmptyResult
     */
    public function edit(Common\Define\Model $model, array $new_data): Model\Venta\Reservation
    {
        return $this->update($model, ['buyer_rut', 'date', 'broker_rut'], $new_data);
    }

    public function load(array $data_row): Model\Venta\Reservation
    {
        $model = parent::load($data_row);

        $this->fetchUnits($model);
        $this->fetchPromotions($model);
        return $model;
    }

    /**
     * @param int $buyer_rut
     * @param DateTimeInterface $date
     * @return Model\Venta\Reservation
     * @throws Common\Implement\Exception\EmptyResult
     */
    public function fetchByBuyerAndDate(int $buyer_rut, DateTimeInterface $date): Model\Venta\Reservation
    {
        $query = $this->connection->getQueryBuilder()
            ->select()
            ->from('reservations')
            ->where('buyer_rut = :buyer_rut AND date >= :date');
        return $this->fetchOne($query, ['buyer_rut' => $buyer_rut, 'date' => $date->sub(new DateInterval('P10D'))->format('Y-m-d')]);
    }

    protected function saveUnits(Model\Venta\Reservation $reservation): void
    {
        if (empty($reservation->units)) {
            return;
        }
        $queryCheck = $this->connection->getQueryBuilder()
            ->select('COUNT(id) AS cnt')
            ->from('reservation_data')
            ->where('reservation_id = :id AND type = "Unit" AND reference_id = :unit_id');
        $statementCheck = $this->connection->prepare($queryCheck);
        $queryInsert = $this->connection->getQueryBuilder()
            ->insert()
            ->into('reservation_data')
            ->columns(['reservation_id', 'type', 'reference_id', 'value'])
            ->values([':reservation_id', ':type', ':reference_id', ':value']);
        $statementInsert = $this->connection->prepare($queryInsert);
        foreach ($reservation->units as $unit) {
            $statementCheck->execute(['id' => $reservation->id, 'unit_id' => $unit['unit']->id]);
            $result = $statementCheck->fetch(PDO::FETCH_ASSOC);
            if ($result['cnt'] > 0) {
                continue;
            }
            $statementInsert->execute(['reservation_id' => $reservation->id, 'type' => 'Unit', 'reference_id' => $unit['unit']->id, 'value' => $unit['value']]);
        }
    }
    protected function savePromotions(Model\Venta\Reservation $reservation): void
    {
        if (empty($reservation->promotions)) {
            return;
        }
        $queryCheck = $this->connection->getQueryBuilder()
            ->select('COUNT(id) AS cnt')
            ->from('reservation_data')
            ->where('reservation_id = :id AND type = "Promotion" AND reference_id = :promotion_id');
        $statementCheck = $this->connection->prepare($queryCheck);
        $queryInsert = $this->connection->getQueryBuilder()
            ->insert()
            ->into('reservation_data')
            ->columns(['reservation_id', 'type', 'reference_id'])
            ->values([':reservation_id', ':type', ':reference_id']);
        $statementInsert = $this->connection->prepare($queryInsert);
        foreach ($reservation->promotions as $promotion) {
            $statementCheck->execute(['id' => $reservation->id, 'promotion_id' => $promotion->id]);
            $result = $statementCheck->fetch(PDO::FETCH_ASSOC);
            if ($result['cnt'] > 0) {
                continue;
            }
            $statementInsert->execute(['reservation_id' => $reservation->id, 'type' => 'Promotion', 'reference_id' => $promotion->id]);
        }
    }
    protected function editUnits(Model\Venta\Reservation $reservation, array $new_data): void
    {
        $querySelect = $this->connection->getQueryBuilder()
            ->select()
            ->from('reservation_data')
            ->where('reservation_id = :id AND type = "Unit" AND reference_id = :unit_id');
        $statementSelect = $this->connection->prepare($querySelect);
        $queryUpdate = $this->connection->getQueryBuilder()
            ->update('reservation_data')
            ->set('value = :value')
            ->where('reservation_id = :id AND type = "Unit" AND reference_id = :unit_id');
        $statementUpdate = $this->connection->prepare($queryUpdate);
        foreach ($new_data as $unit_id => $value) {
            $idx = $reservation->findUnit($unit_id);
            if ($idx === null) {
                $reservation->addUnit($this->unitRepository->fetchById($unit_id), $value);
                continue;
            }
            $statementSelect->execute(['id' => $reservation->id, 'unit_id' => $unit_id]);
            $result = $statementSelect->fetch(PDO::FETCH_ASSOC);
            if (!$result) {
                continue;
            }
            $statementUpdate->execute(['id' => $reservation->id, 'unit_id' => $unit_id, 'value' => $value]);
            $reservation->units[$idx]['value'] = $value;
        }
    }
    protected function editPromotions(Model\Venta\Reservation $reservation, array $new_data): void
    {
        $querySelect = $this->connection->getQueryBuilder()
            ->select()
            ->from('reservation_data')
            ->where('reservation_id = :id AND type = "Promotion" AND reference_id = :promotion_id');
        $statementSelect = $this->connection->prepare($querySelect);
        $queryUpdate = $this->connection->getQueryBuilder()
            ->update('reservation_data')
            ->set('value = :value')
            ->where('reservation_id = :id AND type = "Promotion" AND reference_id = :promotion_id');
        $statementUpdate = $this->connection->prepare($queryUpdate);
        foreach ($new_data as $promotion_id => $value) {
            $idx = array_search($promotion_id, array_column($reservation->promotions, 'id'));
            if ($idx === false) {
                $reservation->promotions []= $this->promotionRepository->fetchById($promotion_id);
                continue;
            }
            $statementSelect->execute(['id' => $reservation->id, 'promotion_id' => $promotion_id]);
            $result = $statementSelect->fetch(PDO::FETCH_ASSOC);
            if (!$result) {
                continue;
            }
            $statementUpdate->execute(['id' => $reservation->id, 'promotion_id' => $promotion_id, 'value' => $value]);
            $reservation->promotions[$idx] = $this->promotionRepository->fetchById($promotion_id);
        }
    }
    protected function fetchUnits(Model\Venta\Reservation &$reservation): Model\Venta\Reservation
    {
        $query = $this->connection->getQueryBuilder()
            ->select()
            ->from('reservation_data')
            ->where('reservation_id = :id AND type = "Unit"');
        $statement = $this->connection->execute($query, ['id' => $reservation->id]);

        while ($result = $statement->fetch(PDO::FETCH_ASSOC)) {
            try {
                $reservation->addUnit($this->unitRepository->fetchById($result['reference_id']), $result['value']);
            } catch (Common\Implement\Exception\EmptyResult) {}
        }
        return $reservation;
    }
    protected function fetchPromotions(Model\Venta\Reservation $reservation): Model\Venta\Reservation
    {
        $query = $this->connection->getQueryBuilder()
            ->select()
            ->from('reservation_data')
            ->where('type = "Promotion" AND reservation_id = :id');
        $statement = $this->connection->execute($query, ['id' => $reservation->id]);
        while ($result = $statement->fetch(PDO::FETCH_ASSOC)) {
            try {
                $reservation->promotions []= $this->promotionRepository->fetchById($result['reference_id']);
            } catch (Common\Implement\Exception\EmptyResult) {}
        }
        return $reservation;
    }
}
