<?php
namespace Incoviba\Service\Proyecto;

use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Common\Implement\Repository\Factory;
use Incoviba\Exception\ServiceAction;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service;

class Broker extends Ideal\Service
{
    public function __construct(LoggerInterface $logger,
                                protected Repository\Proyecto\Broker $brokerRepository,
                                protected Repository\Proyecto\Broker\Data $dataRepository,
                                protected Repository\Proyecto\Broker\Contact $contactRepository,
                                protected Repository\Proyecto\Broker\Contract $contractRepository,
                                protected Service\Proyecto\Broker\Contract $contractService)
    {
        parent::__construct($logger);
    }

    /**
     * @param array $data
     * @return Model\Proyecto\Broker
     * @throws ServiceAction\Create
     */
    public function add(array $data): Model\Proyecto\Broker
    {
        try {
            $broker = $this->brokerRepository->fetchById($data['rut']);
        } catch (EmptyResult) {
            $filteredData = $this->brokerRepository->filterData($data);
            try {
                $broker = $this->brokerRepository->create($filteredData);
                $broker = $this->brokerRepository->save($broker);
            }  catch (PDOException $exception) {
                throw new ServiceAction\Create(__CLASS__, $exception);
            }
        }
        $this->addData($broker, $data);
        return $this->process($broker);
    }
    /**
     * @param int $id
     * @return Model\Proyecto\Broker
     * @throws ServiceAction\Read
     */
    public function get(int $id): Model\Proyecto\Broker
    {
        try {
            return $this->process($this->brokerRepository->fetchById($id));
        } catch (EmptyResult $exception) {
            throw new ServiceAction\Read(__CLASS__, $exception);
        }
    }
    /**
     * @param null|string|array $orderBy
     * @return array
     */
    public function getAll(null|string|array $orderBy = null): array
    {
        try {
            return array_map([$this, 'process'], $this->brokerRepository->fetchAll($orderBy));
        } catch (EmptyResult) {
            return [];
        }
    }
    /**
     * @param array $data
     * @return Model\Proyecto\Broker
     * @throws ServiceAction\Update
     */
    public function edit(array $data): Model\Proyecto\Broker
    {
        try {
            $broker = $this->brokerRepository->fetchById($data['rut']);
        } catch (EmptyResult $exception) {
            throw new ServiceAction\Update(__CLASS__, $exception);
        }
        $filteredData = $this->brokerRepository->filterData($data);
        try {
            $broker = $this->brokerRepository->edit($broker, $filteredData);
            $this->editData($broker, $data);
        } catch (PDOException | EmptyResult) {
        } finally {
            return $this->process($broker);
        }
    }
    /**
     * @param int $broker_rut
     * @return Model\Proyecto\Broker
     * @throws ServiceAction\Delete
     */
    public function delete(int $broker_rut): Model\Proyecto\Broker
    {
        try {
            $broker = $this->brokerRepository->fetchById($broker_rut);
        } catch (EmptyResult $exception) {
            throw new ServiceAction\Delete(__CLASS__, $exception);
        }
        try {
            $this->brokerRepository->remove($broker);
        } catch (PDOException $exception) {
            throw new ServiceAction\Delete(__CLASS__, $exception);
        } finally {
            return $broker;
        }
    }

    protected function process(Model\Proyecto\Broker $broker): Model\Proyecto\Broker
    {
        $broker->addFactory('data', (new Factory())
                ->setArgs(['broker_rut' => $broker->rut])
                ->setCallable([$this->dataRepository, 'fetchByBroker']))
            ->addFactory('contracts', (new Factory())
                ->setArgs(['broker_rut' => $broker->rut])
                ->setCallable([$this->contractService, 'getByBroker']));

        return $broker;
    }

    /**
     * @param Model\Proyecto\Broker $broker
     * @param array $data
     * @return Model\Proyecto\Broker\Data
     * @throws ServiceAction\Create
     */
    protected function addData(Model\Proyecto\Broker $broker, array $data): Model\Proyecto\Broker\Data
    {
        $data['broker_rut'] = $broker->rut;
        $filteredData = $this->dataRepository->filterData($data);
        if (isset($data['contact'])) {
            try {
                $representative = $this->contactRepository->fetchByName($data['contact']);
                $filteredData['representative_id'] = $representative->id;
            } catch (EmptyResult) {
                $representativeData = $this->contactRepository->filterData($data);
                $representativeData['name'] = $data['contact'];
                $representative = $this->contactRepository->create($representativeData);
                try {
                    $representative = $this->contactRepository->save($representative);
                    $filteredData['representative_id'] = $representative->id;
                } catch (PDOException) {
                    unset($representative);
                }
            }
        }
        if (isset($filteredData['representative_id'])) {
            try {
                $this->contactRepository->fetchById($filteredData['representative_id']);
            } catch (EmptyResult) {
                unset($filteredData['representative_id']);
            }
        }
        try {
            $brokerData = $this->dataRepository->fetchByBroker($broker->rut);
            return $this->dataRepository->edit($brokerData, $filteredData);
        } catch (EmptyResult) {}
        try {
            $brokerData = $this->dataRepository->create($filteredData);
            return $this->dataRepository->save($brokerData);
        } catch (PDOException $exception) {
            throw new ServiceAction\Create(__CLASS__, $exception);
        }
    }

    /**
     * @param Model\Proyecto\Broker $broker
     * @param array $data
     * @return Model\Proyecto\Broker\Data
     * @throws ServiceAction\Update
     */
    protected function editData(Model\Proyecto\Broker $broker, array $data): Model\Proyecto\Broker\Data
    {
        try {
            $brokerData = $this->dataRepository->fetchByBroker($broker->rut);
        } catch (EmptyResult $exception) {
            throw new ServiceAction\Update(__CLASS__, $exception);
        }
        if (isset($data['contact'])) {
            try {
                $representative = $this->contactRepository->fetchByName($data['contact']);
                $data['representative_id'] = $representative->id;
            } catch (EmptyResult) {
                $representativeData = $this->contactRepository->filterData($data);
                $representativeData['name'] = $data['contact'];
                try {
                    $representative = $this->contactRepository->create($representativeData);
                    $representative = $this->contactRepository->save($representative);
                    $data['representative_id'] = $representative->id;
                } catch (PDOException) {
                    unset($representative);
                    unset($data['contact']);
                }
            }
        }
        try {
            $data['broker_rut'] = $broker->rut;
            $filteredData = $this->dataRepository->filterData($data);
            $brokerData = $this->dataRepository->edit($brokerData, $filteredData);
            return $this->dataRepository->save($brokerData);
        } catch (PDOException | EmptyResult) {
            return $brokerData;
        }
    }
}
