<?php
namespace Incoviba\Service\Proyecto\Broker;

use DateTimeInterface;
use DateTimeImmutable;
use DateMalformedStringException;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
use Incoviba\Exception\ServiceAction;
use Incoviba\Model;
use Incoviba\Repository;

class Contract extends Ideal\Service
{
    public function __construct(LoggerInterface $logger,
                                protected Repository\Proyecto\Broker\Contract $contractRepository,
                                protected Repository\Proyecto\Broker\Contract\State $stateRepository,
                                protected Repository\Venta\Promotion $promotionRepository)
    {
        parent::__construct($logger);
    }

    public function getAll(): array
    {
        try {
            return array_map([$this, 'process'], $this->contractRepository->fetchAll());
        } catch (Implement\Exception\EmptyResult) {
            return [];
        }
    }

    /**
     * @param int $project_id
     * @return array
     * @throws ServiceAction\Read
     */
    public function getByProject(int $project_id): array
    {
        try {
            return array_map([$this, 'process'], $this->contractRepository->fetchByProject($project_id));
        } catch (Implement\Exception\EmptyResult $exception) {
            throw new ServiceAction\Read(__CLASS__, $exception);
        }
    }

    /**
     * @param int $broker_rut
     * @return array
     * @throws ServiceAction\Read
     */
    public function getByBroker(int $broker_rut): array
    {
        try {
            return array_map([$this, 'process'], $this->contractRepository->fetchByBroker($broker_rut));
        } catch (Implement\Exception\EmptyResult $exception) {
            throw new ServiceAction\Read(__CLASS__, $exception);
        }
    }

    /**
     * @throws ServiceAction\Read
     */
    public function getById(int $id): Model\Proyecto\Broker\Contract
    {
        try {
            return $this->process($this->contractRepository->fetchById($id));
        } catch (Implement\Exception\EmptyResult $exception) {
            throw new ServiceAction\Read(__CLASS__, $exception);
        }
    }

    /**
     * @throws ServiceAction\Create
     */
    public function add(array $data): Model\Proyecto\Broker\Contract
    {
        try {
            return $this->process($this->contractRepository->fetchActiveByProjectAndBroker($data['project_id'], $data['broker_rut']));
        } catch (Implement\Exception\EmptyResult) {}

        try {
            $filteredData = $this->contractRepository->filterData($data);
            $contract = $this->contractRepository->create($filteredData);
            $contract = $this->contractRepository->save($contract);
            $type = Model\Proyecto\Broker\Contract\State\Type::ACTIVE->value;
            $date = new DateTimeImmutable();
            if (isset($data['date'])) {
                try {
                    $date = new DateTimeImmutable($data['date']);
                } catch (DateMalformedStringException) {}
            }
            $state = $this->stateRepository->create(['contract_id' => $contract->id, 'date' => $date, 'type' => $type]);
            $this->stateRepository->save($state);
            return $this->process($contract);
        } catch (PDOException $exception) {
            throw new ServiceAction\Create(__CLASS__, $exception);
        }
    }

    /**
     * @throws ServiceAction\Update
     */
    public function edit(Model\Proyecto\Broker\Contract $contract, array $data): Model\Proyecto\Broker\Contract
    {
        try {
            $filteredData = $this->contractRepository->filterData($data);
            return $this->process($this->contractRepository->edit($contract, $filteredData));
        } catch (PDOException | Implement\Exception\EmptyResult) {
            throw new ServiceAction\Update(__CLASS__);
        }
    }

    /**
     * @throws ServiceAction\Delete
     */
    public function delete(int $id): Model\Proyecto\Broker\Contract
    {
        try {
            $contract = $this->contractRepository->fetchById($id);
            $this->contractRepository->remove($contract);
            return $contract;
        } catch (PDOException | Implement\Exception\EmptyResult $exception) {
            throw new ServiceAction\Delete(__CLASS__, $exception);
        }
    }

    /**
     * @throws ServiceAction\Update
     */
    public function inactive(Model\Proyecto\Broker\Contract $contract, DateTimeInterface $date = new DateTimeImmutable()): Model\Proyecto\Broker\Contract
    {
        try {
            $type = Model\Proyecto\Broker\Contract\State\Type::INACTIVE;
            $state = $this->stateRepository->create(['contract_id' => $contract->id, 'date' => $date, 'type' => $type]);
            $this->stateRepository->save($state);
            return $this->process($contract);
        } catch (PDOException $exception) {
            throw new ServiceAction\Update(__CLASS__, $exception);
        }
    }

    protected function process(Model\Proyecto\Broker\Contract $contract): Model\Proyecto\Broker\Contract
    {
        $contract->addFactory('states', (new Implement\Repository\Factory())
            ->setCallable([$this->stateRepository, 'fetchByContract'])
            ->setArgs(['contract_id' => $contract->id]));
        $contract->addFactory('promotions', (new Implement\Repository\Factory())
            ->setCallable([$this->promotionRepository, 'fetchByContract'])
            ->setArgs(['contract_id' => $contract->id]));
        return $contract;
    }
}
