<?php
/**
 * This file is part of FacturaScripts
 * Copyright (C) 2017-2026 Carlos Garcia Gomez <carlos@facturascripts.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

namespace FacturaScripts\Core\Controller;

use FacturaScripts\Core\DataSrc\Divisas;
use FacturaScripts\Core\DataSrc\Empresas;
use FacturaScripts\Core\DataSrc\FormasPago;
use FacturaScripts\Core\DataSrc\Paises;
use FacturaScripts\Core\DataSrc\Series;
use FacturaScripts\Core\Tools;
use FacturaScripts\Core\Where;
use FacturaScripts\Core\Lib\ExtendedController\ProvinceCityFilterTrait;
use FacturaScripts\Dinamic\Lib\ExtendedController\ListBusinessDocument;
use FacturaScripts\Dinamic\Model\FacturaCliente;
use FacturaScripts\Dinamic\Model\SecuenciaDocumento;

/**
 * Controller to list the items in the FacturaCliente model
 *
 * @author Carlos García Gómez           <carlos@facturascripts.com>
 * @author Jose Antonio Cuello Principal <yopli2000@gmail.com>
 * @author Raul Jimenez                  <raul.jimenez@nazcanetworks.com>
 * @author Cristo M. Estévez Hernández   <cristom.estevez@gmail.com>
 */
class ListFacturaCliente extends ListBusinessDocument
{
    use ProvinceCityFilterTrait;

    public function getPageData(): array
    {
        $data = parent::getPageData();
        $data['menu'] = 'sales';
        $data['title'] = 'invoices';
        $data['icon'] = 'fa-solid fa-file-invoice-dollar';
        return $data;
    }

    protected function createViews(): void
    {
        // listado de facturas de cliente
        $this->createViewSales('ListFacturaCliente', 'FacturaCliente', 'invoices');

        // si el usuario solamente tiene permiso para ver lo suyo, no añadimos el resto de pestañas
        if ($this->permissions->onlyOwnerData) {
            return;
        }

        // líneas de facturas de cliente
        $this->createViewLines('ListLineaFacturaCliente', 'LineaFacturaCliente');

        // recibos de cliente
        $this->createViewReceipts();

        // facturas rectificativas
        $this->createViewRefunds();
    }

    protected function createViewReceipts(string $viewName = 'ListReciboCliente'): void
    {
        $this->addView($viewName, 'ReciboCliente', 'receipts', 'fa-solid fa-dollar-sign')
            ->addOrderBy(['codcliente'], 'customer-code')
            ->addOrderBy(['fecha', 'idrecibo'], 'date')
            ->addOrderBy(['fechapago'], 'payment-date')
            ->addOrderBy(['vencimiento'], 'expiration', 2)
            ->addOrderBy(['importe'], 'amount')
            ->addSearchFields(['codigofactura', 'observaciones'])
            ->setSettings('btnNew', false);

        // filtros
        if (Empresas::count() > 1) {
            $this->addFilterSelect($viewName, 'idempresa', 'company', 'idempresa', Empresas::codeModel());
        }

        $this->addFilterPeriod($viewName, 'expiration', 'expiration', 'vencimiento');
        $this->addFilterNumber($viewName, 'min-total', 'amount', 'importe', '>=');
        $this->addFilterNumber($viewName, 'max-total', 'amount', 'importe', '<=');
        $this->addFilterAutocomplete($viewName, 'codcliente', 'customer', 'codcliente', 'Cliente');
        $this->addFilterPeriod($viewName, 'payment-date', 'payment-date', 'fechapago');

        $payMethods = FormasPago::codeModel();
        if (count($payMethods) > 2) {
            $this->addFilterSelect($viewName, 'codpago', 'payment-method', 'codpago', $payMethods);
        }

        $this->addFilterSelectWhere($viewName, 'status', [
            ['label' => Tools::trans('paid-or-unpaid'), 'where' => []],
            ['label' => '------', 'where' => []],
            ['label' => Tools::trans('paid'), 'where' => [Where::eq('pagado', true)]],
            ['label' => Tools::trans('unpaid'), 'where' => [Where::eq('pagado', false)]],
            ['label' => Tools::trans('expired-receipt'), 'where' => [Where::eq('vencido', true)]],
        ]);

        $currencies = Divisas::codeModel();
        if (count($currencies) > 2) {
            $this->addFilterSelect($viewName, 'coddivisa', 'currency', 'coddivisa', $currencies);
        }

        // botones
        $this->addButtonPayReceipt($viewName);
    }

    protected function createViewRefunds(string $viewName = 'ListFacturaCliente-rect'): void
    {
        $this->addView($viewName, 'FacturaCliente', 'refunds', 'fa-solid fa-share-square')
            ->addSearchFields(['codigo', 'codigorect', 'numero2', 'observaciones'])
            ->addOrderBy(['fecha', 'idfactura'], 'date', 2)
            ->addOrderBy(['total'], 'total')
            ->disableColumn('original', false)
            ->setSettings('btnNew', false);

        // filtro de fecha
        $this->addFilterPeriod($viewName, 'date', 'period', 'fecha');

        // añadimos un filtro select where para forzar las que tienen idfacturarect
        $this->addFilterSelectWhere($viewName, 'idfacturarect', [
            [
                'label' => Tools::trans('rectified-invoices'),
                'where' => [Where::isNotNull('idfacturarect')]
            ]
        ]);
    }

    protected function createViewSales(string $viewName, string $modelName, string $label): void
    {
        parent::createViewSales($viewName, $modelName, $label);

        $this->listView($viewName)->addSearchFields(['codigorect']);

        // filtros
        $paises = Paises::codeModel();
        $this->addFilterSelect($viewName, 'country', 'country', 'codpais', $paises);
        $this->addFilterSelectAuto($viewName, 'provincia', 'province', 'provincia', 'provincias');
        $this->addFilterSelectAuto($viewName, 'ciudad', 'city', 'ciudad', 'ciudades');

        $this->addFilterSelectWhere($viewName, 'status', [
            ['label' => Tools::trans('paid-or-unpaid'), 'where' => []],
            ['label' => '------', 'where' => []],
            ['label' => Tools::trans('paid'), 'where' => [Where::eq('pagada', true)]],
            ['label' => Tools::trans('unpaid'), 'where' => [Where::eq('pagada', false)]],
            ['label' => Tools::trans('expired-receipt'), 'where' => [Where::eq('vencida', true)]],
        ]);
        $this->addFilterCheckbox($viewName, 'idasiento', 'invoice-without-acc-entry', 'idasiento', 'IS', null);

        // agrupamos las acciones en un dropdown
        $this->tab($viewName)->addButtonGroup([
            'name' => 'invoice-actions',
            'icon' => 'fa-solid fa-circle-check',
            'label' => 'actions'
        ]);
        $this->addButtonPayInvoice($viewName, 'invoice-actions');
        $this->addButtonLockInvoice($viewName, 'invoice-actions');
        $this->addButtonGenerateAccountingInvoices($viewName, null, 'invoice-actions');

        // añadimos botón para buscar huecos en las facturas, si el usuario tiene permiso
        if (false === $this->permissions->onlyOwnerData) {
            $this->tab($viewName)
                ->addButton([
                    'action' => 'look-for-gaps',
                    'icon' => 'fa-solid fa-exclamation-triangle',
                    'label' => 'look-for-gaps',
                    'group' => 'invoice-actions'
                ]);
        }
    }

    protected function execAfterAction($action)
    {
        parent::execAfterAction($action);
        if ($action === 'look-for-gaps') {
            $this->lookForGapsAction();
        }
    }

    protected function lookForGaps(SecuenciaDocumento $sequence): array
    {
        $gaps = [];
        $number = $sequence->inicio;

        // buscamos todas las facturas de cliente de la secuencia
        $where = [
            Where::eq('codserie', $sequence->codserie),
            Where::eq('idempresa', $sequence->idempresa)
        ];
        if ($sequence->codejercicio) {
            $where[] = Where::eq('codejercicio', $sequence->codejercicio);
        }
        $db_type = Tools::config('db_type');
        $orderBy = strtolower($db_type) == 'postgresql' ?
            ['CAST(numero as integer)' => 'ASC'] :
            ['CAST(numero as unsigned)' => 'ASC'];
        foreach (FacturaCliente::all($where, $orderBy) as $invoice) {
            // si el número de la factura es menor que el de la secuencia, saltamos
            if ($invoice->numero < $sequence->inicio) {
                continue;
            }

            // si el número de la factura es el esperado, actualizamos el número esperado
            if ($invoice->numero == $number) {
                $number++;
                continue;
            }

            // si el número de la factura es mayor que el esperado, añadimos huecos hasta el número
            while ($invoice->numero > $number) {
                $gaps[] = [
                    'idempresa' => $invoice->idempresa,
                    'codejercicio' => $invoice->codejercicio,
                    'codserie' => $invoice->codserie,
                    'fecha' => $invoice->fecha,
                    'numero' => $number,
                ];
                $number++;
            }
            $number++;
        }

        return $gaps;
    }

    protected function lookForGapsAction(): void
    {
        // buscar huecos escanea todas las facturas, así que solo se permite a
        // quien puede ver datos ajenos (igual que la visibilidad del botón)
        if ($this->permissions->onlyOwnerData) {
            return;
        } elseif (false === $this->validateFormToken()) {
            return;
        }

        $gapsInRange = [];
        $gapsOutOfRange = [];

        // buscamos todas las secuencias de facturas de cliente que usen huecos
        $where = [
            Where::eq('tipodoc', 'FacturaCliente'),
            Where::eq('usarhuecos', true)
        ];
        foreach (SecuenciaDocumento::all($where) as $sequence) {
            // buscamos los huecos de esta secuencia
            $gaps = $this->lookForGaps($sequence);

            // clasificamos los huecos según estén dentro o fuera del límite de 1000
            foreach ($gaps as $gap) {
                if ($gap['numero'] >= $sequence->numero - 1000) {
                    $gapsInRange[] = $gap;
                } else {
                    $gapsOutOfRange[] = $gap;
                }
            }
        }

        // si no hemos encontrado huecos, mostramos un mensaje
        if (empty($gapsInRange) && empty($gapsOutOfRange)) {
            Tools::log()->notice('no-gaps-found');
            return;
        }

        // mostramos primero los huecos que SÍ se rellenarán automáticamente
        if (!empty($gapsInRange)) {
            foreach ($gapsInRange as $gap) {
                Tools::log()->warning('gap-found', [
                    '%codserie%' => Series::get($gap['codserie'])->descripcion,
                    '%numero%' => $gap['numero'],
                    '%fecha%' => $gap['fecha'],
                    '%idempresa%' => Empresas::get($gap['idempresa'])->nombrecorto
                ]);
            }
        }

        // mostramos después los huecos que NO se rellenarán automáticamente
        if (!empty($gapsOutOfRange)) {
            Tools::log()->error('gaps-out-of-range', ['%count%' => count($gapsOutOfRange)]);
            foreach ($gapsOutOfRange as $gap) {
                Tools::log()->error('gap-found-not-fillable', [
                    '%codserie%' => Series::get($gap['codserie'])->descripcion,
                    '%numero%' => $gap['numero'],
                    '%fecha%' => $gap['fecha'],
                    '%idempresa%' => Empresas::get($gap['idempresa'])->nombrecorto
                ]);
            }
        }
    }
}
