<?php
namespace Incoviba\Service\Contabilidad;

use PDOException;
use DateTimeImmutable;
use DateTimeInterface;
use Incoviba\Common\Define\Cartola\Banco;
use Incoviba\Common\Define\Contabilidad\Exporter;
use Incoviba\Common\Ideal\Service;
use Incoviba\Common\Implement\Exception;
use Incoviba\Model;
use Incoviba\Repository;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Log\LoggerInterface;

class Cartola extends Service
{
    public function __construct(LoggerInterface                              $logger,
                                protected StreamFactoryInterface             $streamFactory, protected Exporter $exporter,
                                protected Repository\Inmobiliaria            $inmobiliariaRepository,
                                protected Repository\Inmobiliaria\Cuenta     $cuentaRepository,
                                protected Repository\Contabilidad\Movimiento $movimientoRepository,
                                protected Movimiento                         $movimientoService,
                                protected Repository\Contabilidad\Cartola    $cartolaRepository) {
        parent::__construct($logger);
    }

    protected array $bancos;
    public function register(string $name, Banco $banco): Cartola
    {
        $this->bancos[$name] = $banco;
        return $this;
    }
    public function process(Model\Contabilidad\Banco $banco, UploadedFileInterface $file): array
    {
        return $this->bancos[strtolower($banco->nombre)]->process($file);
    }
    public function export(Model\Inmobiliaria $inmobiliaria, Model\Contabilidad\Banco $banco, DateTimeInterface $mes, array $movimientos): string
    {
        return $this->exporter->export($inmobiliaria, $banco, $mes, $movimientos);
    }
    public function diaria(Model\Inmobiliaria\Cuenta $cuenta, DateTimeInterface $fecha, UploadedFileInterface $file): array
    {
        $ms = $this->getMovimientosDiarios($cuenta->banco, $file);

        $this->groupedMovimientos = [];
        $movimientos = [];
        foreach ($ms as $m) {
            $movimiento = $this->buildMovimiento($cuenta, $m);
            $movimiento = $this->movimientoService->process($movimiento);
            $this->groupMovimientoByDay($movimiento);
            if ($movimiento->fecha->getTimestamp() === $fecha->getTimestamp()) {
                $movimientos []= $movimiento;
            }
        }
        foreach ($this->groupedMovimientos as $timestamp => $movimientos) {
            $cartolaData = [
                'cargos' => 0,
                'abonos' => 0,
                'saldo' => last($movimientos)->saldo
            ];
            foreach ($movimientos as $movimiento) {
                $cartolaData['cargos'] += $movimiento->cargo;
                $cartolaData['abonos'] += $movimiento->abono;
            }
            $this->buildCartola($cuenta, last($movimientos)->fecha, $cartolaData);
        }
        $cartola = $this->cartolaRepository->fetchByCuentaAndFecha($cuenta->id, $fecha);
        return compact('cartola', 'movimientos');
    }
    public function diariaManual(Model\Inmobiliaria\Cuenta $cuenta, DateTimeInterface $fecha, array $data): array
    {
        $cartolaData = [
            'cargos' => 0,
            'abonos' => 0,
            'saldos' => 0
        ];
        $movimientos = [];
        foreach ($data as $row) {
            $dataMovimiento = $row;
            $dataMovimiento['fecha'] = $fecha->format('Y-m-d');
            $dataMovimiento['documento'] = '';
            $movimiento = $this->buildMovimiento($cuenta, $dataMovimiento);
            $movimiento = $this->movimientoService->process($movimiento);

            $movimientos []= $movimiento;
            $cartolaData['cargos'] += $movimiento->cargo;
            $cartolaData['abonos'] += $movimiento->abono;
            $cartolaData['saldo'] = $movimiento->saldo;
        }
        $cartola = $this->buildCartola($cuenta, $fecha, $cartolaData);
        return compact('cartola', 'movimientos');
    }

    public function import(int $cuenta_id, UploadedFileInterface $file): array
    {
        $cuenta = $this->cuentaRepository->fetchById($cuenta_id);
        $movimientos = $this->process($cuenta->banco, $file);

        $inmobiliaria = $cuenta->inmobiliaria;

        $addedMovimientos = [];
        foreach ($movimientos as &$dataMovimiento) {
            $dataMovimiento['cuenta_id'] = $cuenta->id;
            if (array_key_exists('centro_costo', $dataMovimiento) and $dataMovimiento['centro_costo'] !== 0) {
                $dataMovimiento['centro_costo_id'] = $dataMovimiento['centro_costo'];
            }
            $dataMovimiento['fecha'] = new DateTimeImmutable($dataMovimiento['fecha']);
            if (array_key_exists('rut', $dataMovimiento)) {
                $ruts = $this->parseRut($dataMovimiento['rut']);
                if (key_exists('rut', $ruts)) {
                    $dataMovimiento['rut'] = $ruts['rut'];
                    $dataMovimiento['digito'] = $ruts['digito'];
                } else {
                    $dataMovimiento['rut'] = $ruts[0]['rut'];
                    $dataMovimiento['digito'] = $ruts[0]['digito'];
                }
            }
            try {
                $movimiento = $this->movimientoRepository
                    ->fetchByCuentaAndFechaAndGlosaAndCargoAndAbonoAndSaldo($dataMovimiento['cuenta_id'], $dataMovimiento['fecha'],
                        $dataMovimiento['glosa'], $dataMovimiento['cargo'] ?? 0,
                        $dataMovimiento['abono'] ?? 0, $dataMovimiento['saldo']);
            } catch (Exception\EmptyResult) {
                $movimiento = $this->movimientoService->add($dataMovimiento);
                $dataMovimiento['nuevo'] = true;
            }
            $dataMovimiento['id'] = $movimiento->id;
            $dataMovimiento['sociedad'] = $inmobiliaria;
            $addedMovimientos []= $movimiento->id;
        }
        $fechas = array_unique(array_map(function($movimiento) {
            return $movimiento['fecha']->format('Y-m-d');
        }, $movimientos));
        foreach ($fechas as $dia) {
            try {
                $this->cartolaRepository->fetchByCuentaAndFecha($cuenta->id, new DateTimeImmutable($dia));
                continue;
            } catch (Exception\EmptyResult) {}
            $movs = array_filter($movimientos, function($movimiento) use ($dia) {
                return $movimiento['fecha'] === $dia;
            });
            $cargos = array_sum(array_map(function($movimiento) {
                return $movimiento['cargo'];
            }, $movs));
            $abonos = array_sum(array_map(function($movimiento) {
                return $movimiento['abono'];
            }, $movs));
            $saldo = last($movs)['saldo'];
            $cartolaData = [
                'cargos' => $cargos,
                'abonos' => $abonos,
                'saldo' => $saldo
            ];
            $this->buildCartola($cuenta, new DateTimeImmutable($dia), $cartolaData);
        }

        $startDate = new DateTimeImmutable(min($fechas));
        $endDate = new DateTimeImmutable(max($fechas));
        $movimientosFaltantes = $this->movimientoService->findMissing($cuenta, $addedMovimientos, $startDate, $endDate);
        $movimientosObsoletos = [];
        if (count($movimientosFaltantes) > 0) {
            $movimientosObsoletos = array_map(function($movimiento) {
                $arr = (array) $movimiento;
                $arr['sociedad'] = $movimiento->cuenta->inmobiliaria;
                $arr['obsoleto'] = true;
                return $arr;
            }, $movimientosFaltantes);
        }

        return array_map(function($movimiento) {
            if (is_a($movimiento['fecha'], DateTimeInterface::class)) {
                $movimiento['fecha'] = $movimiento['fecha']->format('Y-m-d');
            }
            return $movimiento;
        }, array_merge($movimientos, $movimientosObsoletos));
    }
    public function check(): array
    {
        try {
            $cuentas = $this->cuentaRepository->fetchAll();
            $fechas = [];
            foreach ($cuentas as $cuenta) {
                $fechas[$cuenta->id] = $this->checkForCuenta($cuenta);
            }
            return $fechas;
        } catch (Exception\EmptyResult) {
            return [];
        }
    }
    public function checkForCuenta(Model\Inmobiliaria\Cuenta $cuenta): array
    {
        $cartolas = $this->cartolaRepository->fetchByCuenta($cuenta->id);
        $movimientos = $this->movimientoRepository->fetchByCuenta($cuenta->id);
        $fechasMovimientos = array_unique(array_map(function(Model\Contabilidad\Movimiento $movimiento) {
            return $movimiento->fecha->format('Y-m-d');
        }, $movimientos));
        $fechasCartolas = array_map(function(Model\Contabilidad\Cartola $cartola) {
            return $cartola->fecha->format('Y-m-d');
        }, $cartolas);
        $fechas = array_diff($fechasMovimientos, $fechasCartolas);
        return array_values($fechas);
    }
    public function update(array $cuentas): array
    {
        $cartolas = [];
        foreach ($cuentas as $cuenta_id => $fechas) {
            $cuenta = $this->cuentaRepository->fetchById($cuenta_id);
            $cartolas[$cuenta_id] = $this->updateForCuenta($cuenta, $fechas);
        }
        return $cartolas;
    }
    public function updateForCuenta(Model\Inmobiliaria\Cuenta $cuenta, array $fechas): array
    {
        $cartolas = [];
        foreach ($fechas as $fecha) {
            $cartolas []= $this->updateForCuentaAndFecha($cuenta, new DateTimeImmutable($fecha));
        }
        return $cartolas;
    }
    public function updateForCuentaAndFecha(Model\Inmobiliaria\Cuenta $cuenta, DateTimeInterface $fecha): Model\Contabilidad\Cartola
    {
        try {
            return $this->cartolaRepository->fetchByCuentaAndFecha($cuenta->id, $fecha);
        } catch (Exception\EmptyResult) {}
        $movimientos = $this->movimientoRepository->fetchByCuentaAndFecha($cuenta->id, $fecha);
        $cartolaData = [
            'cargos' => array_sum(array_map(function(Model\Contabilidad\Movimiento $movimiento) {
                return $movimiento->cargo;
            }, $movimientos)),
            'abonos' => array_sum(array_map(function(Model\Contabilidad\Movimiento $movimiento) {
                return $movimiento->abono;
            }, $movimientos)),
            'saldo' => last($movimientos)->saldo
        ];
        return $this->buildCartola($cuenta, $fecha, $cartolaData);
    }

    protected function getMovimientosDiarios(Model\Contabilidad\Banco $banco, UploadedFileInterface $file): array
    {
        $movimientos = $this->bancos[strtolower($banco->nombre)]->process($file);
        return $this->bancos[strtolower($banco->nombre)]->processMovimientosDiarios($movimientos);
    }
    protected array $groupedMovimientos = [];
    protected function groupMovimientoByDay(Model\Contabilidad\Movimiento $movimiento): Cartola
    {
        if (!isset($this->groupedMovimientos[$movimiento->fecha->getTimestamp()])) {
            $this->groupedMovimientos[$movimiento->fecha->getTimestamp()] = [];
        }
        $this->groupedMovimientos[$movimiento->fecha->getTimestamp()] []= $movimiento;
        return $this;
    }
    protected function buildCartola(Model\Inmobiliaria\Cuenta $cuenta, DateTimeInterface $fecha, array $data): Model\Contabilidad\Cartola
    {
        try {
            return $this->cartolaRepository->fetchByCuentaAndFecha($cuenta->id, $fecha);
        } catch (Exception\EmptyResult) {
            $data['cuenta_id'] = $cuenta->id;
            $data['fecha'] = $fecha->format('Y-m-d');
            $cartola = $this->cartolaRepository->create($data);
            return $this->cartolaRepository->save($cartola);
        }
    }
    protected function buildMovimiento(Model\Inmobiliaria\Cuenta $cuenta, array $data): Model\Contabilidad\Movimiento
    {
        try {
            return $this->movimientoRepository
                ->fetchByCuentaAndFechaAndGlosaAndCargoAndAbonoAndSaldo(
                    $cuenta->id,
                    new DateTimeImmutable($data['fecha']),
                    $data['glosa'],
                    $data['cargo'] ?? 0,
                    $data['abono'] ?? 0,
                    $data['saldo']
                );
        } catch (Exception\EmptyResult) {
            $data['cuenta_id'] = $cuenta->id;
            $movimiento = $this->movimientoRepository->create($data);
            try {
                return $this->movimientoRepository->save($movimiento);
            } catch (PDOException $exception) {
                $this->logger->critical(var_export($data,true));
                throw $exception;
            }
        }
    }
    protected function parseRut(string $rut): array
    {
        if (str_contains($rut, '/')) {
            $ruts = explode('/', $rut);
            $output = [];
            foreach ($ruts as $rut) {
                $output []= $this->parseRut($rut);
            }
            return $output;
        }
        list($rut, $digito) = explode('-', $rut);
        return ['rut' => trim(preg_replace('/\D+/', '', $rut)), 'digito' => trim($digito)];
    }
}
