<?php
namespace Incoviba\Repository\Venta;

use PDO;
use DateTimeImmutable;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service;

class Factura extends Ideal\Repository
{
    public function __construct(Implement\Connection $connection, protected Repository\Venta $ventaRepository,
                                protected Service\Persona $personaService,
                                protected Repository\Persona $personaRepository, protected Unidad $unidadRepository)
    {
        parent::__construct($connection);
        $this->setTable('facturas');
    }

    public function create(?array $data = null): Model\Venta\Factura
    {
        $map = (new Implement\Repository\MapperParser(['index', 'proporcion']))
            ->register('venta_id', (new Implement\Repository\Mapper())
                ->setProperty('venta')
                ->setFunction(function($data) {
                    return $this->ventaRepository->fetchById($data['venta_id']);
                }))
            ->register('cliente_rut', (new Implement\Repository\Mapper())
                ->setProperty('cliente')
                ->setFunction(function($data) {
                    return $this->personaService->getById($data['cliente_rut']);
                }));
        $factura = $this->parseData(new Model\Venta\Factura(), $data, $map);
        return $this->createDatos($factura, $data);
    }
    public function createDatos(Model\Venta\Factura &$factura, ?array $data = null): Model\Venta\Factura
    {
        try {
            $result = $this->getDatos($factura->venta->id);
            if ($result['fecha'] !== null) {
                $factura->fecha = new DateTimeImmutable($result['fecha']);
            }
            if ($result['uf'] !== null) {
                $factura->uf = json_decode($result['uf']);
                $factura->uf->fecha = new DateTimeImmutable($factura->uf->fecha);
            }
            if ($result['ipc'] !== null) {
                $factura->ipc = json_decode($result['ipc']);
                $factura->ipc->fecha = new DateTimeImmutable($factura->ipc->fecha);
            }
            if ($result['unidades'] !== null) {
                $factura->unidades = array_map(function($datos) use ($factura) {
                    $unidad = $this->unidadRepository->fetchById($datos->unidad_id);
                    return (object) [
                        'unidad' => $unidad,
                        'descripcion' => implode(' ', [ucwords($unidad->proyectoTipoUnidad->tipoUnidad->descripcion), $unidad->descripcion]),
                        'precio' => $datos->precio,
                        'prorrateo' => $datos->prorrateo
                    ];
                }, json_decode($result['unidades']));
            }
            if ($result['terreno'] !== null) {
                $factura->terreno = $result['terreno'];
            }
        } catch (EmptyResult) {
            if (isset($data['fecha'])) {
                $factura->fecha = new DateTimeImmutable($data['fecha']);
            }
            if (isset($data['uf'])) {
                $factura->uf = json_decode($data['uf']);
                $factura->uf->fecha = new DateTimeImmutable($factura->uf->fecha);
            }
            if (isset($data['ipc'])) {
                $factura->ipc = json_decode($data['ipc']);
                $factura->ipc->fecha = new DateTimeImmutable($factura->ipc->fecha);
            }
            if (isset($data['unidades'])) {
                $factura->unidades = array_map(function($datos) use ($factura) {
                    $unidad = $this->unidadRepository->fetchById($datos->unidad_id);
                    return (object) [
                        'unidad' => $unidad,
                        'descripcion' => implode(' ', [ucwords($unidad->proyectoTipoUnidad->tipoUnidad->descripcion), $unidad->descripcion]),
                        'precio' => $datos->precio,
                        'prorrateo' => $datos->prorrateo
                    ];
                }, json_decode($data['unidades']));
            }
            if (isset($data['terreno'])) {
                $factura->terreno = (int) $data['terreno'];
            }
        }

        return $factura;
    }
    protected function getDatos(int $venta_id): array
    {
        $query = $this->connection->getQueryBuilder()
            ->select()
            ->from('venta_datos_facturas')
            ->where('venta_id = :venta_id');
        $result = $this->connection->execute($query, ['venta_id' => $venta_id])->fetch(PDO::FETCH_ASSOC);
        if ($result === false) {
            throw new EmptyResult($query);
        }
        return $result;
    }
    public function save(Define\Model $model): Model\Venta\Factura
    {
        $model->id = $this->saveNew([
            'venta_id',
            'index',
            'proporcion',
            'cliente_rut'
        ], [
            $model->venta->id,
            $model->index,
            $model->proporcion,
            $model->cliente->rut
        ]);
        return $this->saveDatos($model);
    }
    protected function saveDatos(Model\Venta\Factura $factura): Model\Venta\Factura
    {
        try {
            $this->getDatos($factura->venta->id);
        } catch (EmptyResult) {
            $query = $this->connection->getQueryBuilder()
                ->insert()
                ->into('venta_datos_facturas')
                ->columns([
                    'venta_id',
                    'fecha',
                    'unidades',
                    'terreno',
                    'uf',
                    'ipc'
                ])
                ->values([
                    'venta_id' => $factura->venta->id,
                    'fecha' => $factura->fecha?->format('Y-m-d'),
                    'unidades' => (isset($factura->unidades)) ? json_encode(array_map(function($unidad) {
                        return [
                            'unidad_id' => $unidad->unidad->id,
                            'precio' => $unidad->precio,
                            'prorrateo' => $unidad->prorrateo
                        ];
                    }, $factura?->unidades)) : null,
                    'terreno' => $factura?->terreno,
                    'uf' => (isset($factura->uf)) ? json_encode(['fecha' => $factura->uf?->fecha->format('Y-m-d'), 'valor' => $factura->uf?->valor]) : null,
                    'ipc' => (isset($factura->ipc)) ? json_encode(['fecha' => $factura->ipc?->fecha->format('Y-m-d'), 'valor' => $factura->ipc?->valor]) : null
                ]);
            $this->connection->execute($query);
        }

        return $factura;
    }
    public function edit(Define\Model $model, array $new_data): Model\Venta\Factura
    {
        $model = $this->editDatos($model, $new_data);
        return $this->update($model, ['venta_id', 'index', 'proporcion', 'cliente_rut'], $new_data);
    }
    protected function editDatos(Model\Venta\Factura $factura, array $new_data): Model\Venta\Factura
    {
        $dataFields = [
            'fecha',
            'unidades',
            'terreno',
            'uf',
            'ipc'
        ];
        $fields = [];
        $values = [];
        foreach ($dataFields as $field) {
            if (isset($new_data[$field])) {
                $fields[] = "{$field} = ?";
                $values[] = is_array($new_data[$field]) ? json_encode($new_data[$field]) : $new_data[$field];
            }
        }
        if (count($fields) === 0) {
            return $factura;
        }

        $query = $this->connection->getQueryBuilder()
            ->update('venta_datos_facturas')
            ->set($fields)
            ->where('venta_id = ?')
            ->limit(1);
        $this->connection->execute($query, array_merge($values, [$factura->venta->id]));

        return $this->fetchById($factura->id);
    }

    /**
     * @throws EmptyResult
     */
    public function fetchByVenta(int $venta_id): array
    {
        $query = $this->connection->getQueryBuilder()
            ->select()
            ->from($this->getTable())
            ->where('venta_id = :venta_id');
        return $this->fetchMany($query, ['venta_id' => $venta_id]);
    }
    /**
     * @throws EmptyResult
     */
    public function fetchByVentaAndIndex(int $venta_id, int $index): Model\Venta\Factura
    {
        $query = $this->connection->getQueryBuilder()
            ->select()
            ->from($this->getTable())
            ->where('venta_id = :venta_id AND `index` = :index');
        return $this->fetchOne($query, ['venta_id' => $venta_id, 'index' => $index]);
    }
}
