<?php
namespace Incoviba\Service\Contabilidad;

use DateTimeInterface;
use DateTimeImmutable;
use DateMalformedStringException;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception;
use Incoviba\Repository;
use Incoviba\Service;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerInterface;

class Nubox extends Ideal\Service
{
    public function __construct(protected LoggerInterface               $logger,
                                protected Repository\Contabilidad\Nubox $nuboxRepository,
                                protected Service\Redis                 $redisService,
                                protected ClientInterface               $client,
                                protected RequestFactoryInterface       $requestFactory,
                                protected string                        $api_url)
    {
        parent::__construct($logger);
    }

    protected array $tokens;
    public function getToken(int $inmobiliaria_rut): string
    {
        if (!isset($this->tokens[$inmobiliaria_rut])) {
            $redisKey = "token_nubox:{$inmobiliaria_rut}";
            try {
                $this->tokens[$inmobiliaria_rut] = $this->redisService->get($redisKey);
            } catch (Exception\EmptyRedis) {
                $nubox = $this->nuboxRepository->fetchByInmobiliaria($inmobiliaria_rut);
                $request = $this->requestFactory
                    ->createRequest('POST', implode('/', [$this->api_url, 'autenticar']))
                    ->withHeader('Authorization', "Basic {$nubox->getLogin()}")
                    ->withHeader('Content-Type', 'application/json')
                    ->withHeader('Accept', 'application/json');
                $response = $this->client->sendRequest($request);
                $sistemas = json_decode($response->getBody()->getContents(), JSON_OBJECT_AS_ARRAY);

                $this->setToken($inmobiliaria_rut, $response->getHeaderLine('Token'))
                    ->setSistemas($inmobiliaria_rut, $sistemas['Sistemas']);
            }
        }
        return $this->tokens[$inmobiliaria_rut];
    }
    public function setToken(int $inmobiliaria_rut, string $token): Nubox
    {
        $this->tokens[$inmobiliaria_rut] = $token;

        $redisKey = "token_nubox:{$inmobiliaria_rut}";
        $this->redisService->set($redisKey, $this->tokens[$inmobiliaria_rut], 60 * 15);

        return $this;
    }

    protected array $sistemas;
    public function getSistemas(int $inmobiliaria_rut): array
    {
        if (!isset($this->sistemas[$inmobiliaria_rut])) {
            $redisKey = "nubox:{$inmobiliaria_rut}";
            try {
                $this->sistemas[$inmobiliaria_rut] = json_decode($this->redisService->get($redisKey));
            } catch (Exception\EmptyRedis) {
                $this->getToken($inmobiliaria_rut);
            }
        }
        return $this->sistemas[$inmobiliaria_rut];
    }
    public function setSistemas(int $inmobiliaria_rut, array $sistemas): Nubox
    {
        $this->sistemas[$inmobiliaria_rut] = $sistemas;

        $redisKey = "nubox:{$inmobiliaria_rut}";
        $this->redisService->set($redisKey, json_encode($sistemas));

        return $this;
    }

    /**
     * @param int $inmobiliaria_rut
     * @return array
     * @throws ClientExceptionInterface
     * @throws Exception\HttpResponse
     * @throws DateMalformedStringException
     */
    public function getCuentas(int $inmobiliaria_rut): array
    {
        $redisKey = "nubox:cuentas:{$inmobiliaria_rut}";
        try {
            return json_decode($this->redisService->get($redisKey), JSON_OBJECT_AS_ARRAY);
        } catch (Exception\EmptyRedis) {
            $today = new DateTimeImmutable();
            $libro = $this->getLibroMayor($inmobiliaria_rut, new DateTimeImmutable($today->format('Y-m-1')), new DateTimeImmutable($today->format('Y-m-t')));
            $cuentas = array_map(function($cuenta) {
                $full = $cuenta['Cuenta'];
                $space = strpos($full, ' ');
                return ['codigo' => substr($full, 0, $space), 'nombre' => substr($full, $space + 1)];
            }, $libro);
            $this->redisService->set($redisKey, json_encode($cuentas));
            return $cuentas;
        }
    }

    /**
     * @throws DateMalformedStringException
     * @throws ClientExceptionInterface
     * @throws Exception\HttpResponse
     * @throws Exception\EmptyResult
     */
    public function getCuenta(int $inmobiliaria_rut, string $cuentaNombre): string
    {
        $cuentas = $this->getCuentas($inmobiliaria_rut);
        $validas = array_filter($cuentas, function($cuenta) use ($cuentaNombre) {
            return strtolower($cuenta['nombre']) === strtolower($cuentaNombre);
        });
        if (count($validas) === 0) {
            throw new Exception\EmptyResult('Cuenta no encontrada');
        }
        return array_values($validas)[0]['codigo'];
    }

    /**
     * @throws Exception\HttpResponse
     * @throws ClientExceptionInterface
     */
    public function getLibroMayor(int $inmobiliaria_rut, DateTimeInterface $from, DateTimeInterface $to): array
    {
        $inmobiliaria = $this->nuboxRepository->fetchByInmobiliaria($inmobiliaria_rut);
        $query = [
            'NumeroSerie' => 1,
            'CodigoEmpresa' => $inmobiliaria->alias,
            'DDInicio' => $from->format('j'),
            'MMInicio' => $from->format('n'),
            'YYInicio' => $from->format('Y'),
            'DDTermino' => $to->format('j'),
            'MMTermino' => $to->format('n'),
            'YYTermino' => $to->format('Y'),
            'CodigoCentroDeCosto' => 0,
            'CodigoSucursal' => 0,
            'CodigoCuenta' => 0,
            'ModoIFRS' => 'false',
            'CuentasConSaldo' => 'false',
            'IncluirCodigoCuenta' => 'true',
        ];
        $uri = 'contabilidad/libro-mayor?' . http_build_query($query);
        $response = $this->send($uri, $inmobiliaria_rut);
        return json_decode($response->getBody()->getContents(), JSON_OBJECT_AS_ARRAY);
    }
    public function getLibroDiario(int $inmobiliaria_rut, DateTimeInterface $from, DateTimeInterface $to): array
    {
        $inmobiliaria = $this->nuboxRepository->fetchByInmobiliaria($inmobiliaria_rut);
        $query = [
            'ModoIFRS' => 0,
            'DDInicio' => $from->format('j'),
            'MMInicio' => $from->format('n'),
            'YYInicio' => $from->format('Y'),
            'DDTermino' => $to->format('j'),
            'MMTermino' => $to->format('n'),
            'YYTermino' => $to->format('Y'),
            'Sucursal' => 0,
            'codigoEmpresa' => $inmobiliaria->alias,
            'NumeroSerie' => 1,
            'CuentasConSaldo' => 0,
            'incluirCodigoCuenta' => 1
        ];
        $uri = 'contabilidad/libro-diario?' . http_build_query($query);
        $response = $this->send($uri, $inmobiliaria_rut);
        return json_decode($response->getBody()->getContents(), JSON_OBJECT_AS_ARRAY);
    }

    /**
     * @throws DateMalformedStringException
     * @throws Exception\HttpResponse
     * @throws ClientExceptionInterface
     */
    public function getMesCuenta(int $inmobiliaria_rut, string $cuenta, DateTimeInterface $mes): array
    {
        $inmobiliaria = $this->nuboxRepository->fetchByInmobiliaria($inmobiliaria_rut);
        $from = new DateTimeImmutable($mes->format('Y-m-1'));
        $to = new DateTimeImmutable($mes->format('Y-m-t'));
        $query = [
            'NumeroSerie' => 1,
            'CodigoEmpresa' => $inmobiliaria->alias,
            'DDInicio' => $from->format('j'),
            'MMInicio' => $from->format('n'),
            'YYInicio' => $from->format('Y'),
            'DDTermino' => $to->format('j'),
            'MMTermino' => $to->format('n'),
            'YYTermino' => $to->format('Y'),
            'CodigoCentroDeCosto' => 0,
            'CodigoSucursal' => 0,
            'CodigoCuenta' => $cuenta,
            'ModoIFRS' => 'false',
            'CuentasConSaldo' => 'false',
            'IncluirCodigoCuenta' => 'true',
        ];
        $uri = 'contabilidad/libro-mayor?' . http_build_query($query);
        $response = $this->send($uri, $inmobiliaria_rut);
        return json_decode($response->getBody()->getContents(), JSON_OBJECT_AS_ARRAY);
    }
    public function getFacturas(int $inmobiliaria_rut, DateTimeInterface $dia): array
    {
        //$inmobiliaria = $this->nuboxRepository->fetchByInmobiliaria($inmobiliaria_rut);
        $query = [
            'factura',
            'documento',
            '78017310-6',
            'estadoVenta',
            551,
            'FAC-EL',
            1
        ];
        $uri = implode('/', $query);
        $response = $this->send($uri, $inmobiliaria_rut);
        $content = json_decode($response->getBody()->getContents(), JSON_OBJECT_AS_ARRAY);
        if (!is_array($content)) {
            $this->logger->error($content);
            return [];
        }
        return $content;
    }

    /**
     * @throws Exception\HttpResponse
     * @throws ClientExceptionInterface
     */
    private function send(string $uri, int $inmobiliaria_rut, string $method = 'GET', ?StreamInterface $body = null): ResponseInterface
    {
        $request = $this->requestFactory
            ->createRequest($method, implode('/', [$this->api_url, $uri]))
            ->withHeader('token', $this->getToken($inmobiliaria_rut));
        if ($body !== null) {
            $request = $request->withBody($body);
        }
        $response = $this->client->sendRequest($request);
        if ($response->getStatusCode() !== 200) {
            $json = json_decode($response->getBody()->getContents(), JSON_OBJECT_AS_ARRAY);
            $message = $json['Message'] ?? '';
            throw new Exception\HttpResponse($response->getReasonPhrase(), $message, $response->getStatusCode());
        }
        return $response;
    }
}
