<?php
/**
 * This file is part of FacturaScripts
 * Copyright (C) 2014-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\Model;

use FacturaScripts\Core\Model\Base\ExerciseRelationTrait;
use FacturaScripts\Core\Template\ModelClass;
use FacturaScripts\Core\Template\ModelTrait;
use FacturaScripts\Core\Tools;
use FacturaScripts\Core\Where;
use FacturaScripts\Dinamic\Model\CuentaEspecial as DinCuentaEspecial;
use FacturaScripts\Dinamic\Model\Ejercicio as DinEjercicio;
use FacturaScripts\Dinamic\Model\Subcuenta as DinSubcuenta;

/**
 * First level of the accounting plan.
 *
 * @author Carlos García Gómez  <carlos@facturascripts.com>
 * @author Artex Trading sa     <jcuello@artextrading.com>
 */
class Cuenta extends ModelClass
{
    use ModelTrait;
    use ExerciseRelationTrait;

    /** Código identificativo de la cuenta contable. @var string */
    public $codcuenta;

    /** Código de la cuenta especial asociada. @var string */
    public $codcuentaesp;

    /** Importe acumulado en el debe de la cuenta. @var float */
    public $debe;

    /** Descripción de la cuenta contable. @var string */
    public $descripcion;

    /** Indica si se omiten las comprobaciones adicionales del modelo. @var bool */
    private $disable_additional_test = false;

    /** Importe acumulado en el haber de la cuenta. @var float */
    public $haber;

    /** Identificador único de la cuenta contable. @var int */
    public $idcuenta;

    /** Código de la cuenta contable superior. @var string */
    public $parent_codcuenta;

    /** Identificador de la cuenta contable superior. @var int */
    public $parent_idcuenta;

    /** Saldo acumulado de la cuenta contable. @var float */
    public $saldo;

    public function clear(): void
    {
        parent::clear();
        $this->debe = 0.0;
        $this->haber = 0.0;
        $this->saldo = 0.0;
    }

    public function createSubcuenta(string $codsubcuenta, string $description): Subcuenta
    {
        $subcuenta = new DinSubcuenta();
        $subcuenta->codcuenta = $this->codcuenta;
        $subcuenta->codejercicio = $this->codejercicio;
        $subcuenta->codsubcuenta = $codsubcuenta;
        $subcuenta->descripcion = $description;
        $subcuenta->idcuenta = $this->idcuenta;
        $subcuenta->save();

        return $subcuenta;
    }

    public function delete(): bool
    {
        if ($this->getExercise()->isOpened() || $this->disable_additional_test) {
            return parent::delete();
        }

        Tools::log()->warning('closed-exercise', ['%exerciseName%' => $this->getExercise()->nombre]);
        return false;
    }

    public function disableAdditionalTest(bool $value): void
    {
        $this->disable_additional_test = $value;
    }

    /**
     * Returns all children accounts for this account.
     *
     * @return static[]
     */
    public function getChildren(): array
    {
        return $this->allWhereEq('parent_idcuenta', $this->idcuenta, ['codcuenta' => 'ASC']);
    }

    public function getFreeSubjectAccountCode($subject): string
    {
        // nos quedamos solamente con los números del código
        $code = preg_replace('/[^0-9]/', '', $subject->id());
        if (strlen($code) === $this->getExercise()->longsubcuenta) {
            // si el código ya tiene la longitud de una subcuenta, lo usamos como subcuenta
            return $code;
        }

        // conformamos un array con el número del cliente, los 99 primeros números y un número aleatorio
        $numbers = array_merge(
            [$code],
            range(1, 99),
            [rand(100, 9999)]
        );

        // añadimos también los 100 siguientes números al total de subcuentas
        $subcuenta = new Subcuenta();
        $whereTotal = [
            Where::eq('codcuenta', $this->codcuenta),
            Where::eq('codejercicio', $this->codejercicio)
        ];
        $total = $subcuenta->count($whereTotal);
        if ($total > 99) {
            $numbers = array_merge($numbers, range($total, $total + 99));
        }

        // probamos los números para elegir el primer código de subcuenta que no exista
        foreach ($numbers as $num) {
            $newCode = $this->fillToLength($this->getExercise()->longsubcuenta, $num, $this->codcuenta);
            if (empty($newCode)) {
                continue;
            }

            // comprobamos que esta subcuenta no esté en uso en otro cliente o proveedor
            $where = [Where::eq('codsubcuenta', $newCode)];
            $count = $subject->count($where);
            if ($count > 0) {
                continue;
            }

            // si la subcuenta no existe, la elegimos
            $where = [
                Where::eq('codejercicio', $this->codejercicio),
                Where::eq('codsubcuenta', $newCode)
            ];
            if (false === $subcuenta->loadWhere($where)) {
                return $newCode;
            }
        }

        // no hemos encontrado ninguna subcuenta libre
        Tools::log()->error('no-empty-account-found');

        return '';
    }

    /**
     * Returns parent account.
     *
     * @return static
     */
    public function getParent(): self
    {
        $parent = new static();

        // no parent data?
        if (empty($this->parent_idcuenta) && empty($this->parent_codcuenta)) {
            return $parent;
        }

        // parent id?
        if (!empty($this->parent_idcuenta) && $parent->load($this->parent_idcuenta) && $parent->codejercicio === $this->codejercicio) {
            return $parent;
        }

        $where = [
            Where::eq('codejercicio', $this->codejercicio),
            Where::eq('codcuenta', $this->parent_codcuenta)
        ];
        $parent->loadWhere($where);
        return $parent;
    }

    /**
     * Returns all subaccounts from this account.
     *
     * @return DinSubcuenta[]
     */
    public function getSubcuentas(): array
    {
        return DinSubcuenta::allWhereEq('idcuenta', $this->idcuenta, ['codsubcuenta' => 'ASC']);
    }

    public function install(): string
    {
        // force the parents tables
        new DinCuentaEspecial();
        new DinEjercicio();

        return parent::install();
    }

    public static function primaryColumn(): string
    {
        return 'idcuenta';
    }

    public function primaryDescriptionColumn(): string
    {
        return 'codcuenta';
    }

    public function save(): bool
    {
        if ($this->getExercise()->isOpened() || $this->disable_additional_test) {
            return parent::save();
        }

        Tools::log()->warning('closed-exercise', ['%exerciseName%' => $this->getExercise()->nombre]);
        return false;
    }

    public static function tableName(): string
    {
        return 'cuentas';
    }

    public function test(): bool
    {
        $this->codcuenta = trim($this->codcuenta);
        $this->descripcion = Tools::noHtml($this->descripcion);

        if (empty($this->codcuenta) || false === is_numeric($this->codcuenta)) {
            Tools::log()->warning('invalid-number', ['%number%' => $this->codcuenta]);
            return false;
        }

        if (strlen($this->descripcion ?? '') < 1) {
            Tools::log()->warning('field-required', ['%field%' => 'descripcion']);
            return false;
        }

        // prevent loops
        if (!empty($this->parent_idcuenta) && $this->parent_idcuenta === $this->idcuenta) {
            $this->parent_idcuenta = null;
        }
        if (!empty($this->parent_codcuenta) && $this->parent_codcuenta === $this->codcuenta) {
            $this->parent_codcuenta = null;
        }

        // uncompleted parent account data?
        if (!empty($this->parent_idcuenta) || !empty($this->parent_codcuenta)) {
            $parent = $this->getParent();
            $this->parent_codcuenta = $parent->codcuenta;
            $this->parent_idcuenta = $parent->idcuenta;

            // code length must be bigger than the parent
            if (strlen($this->codcuenta) <= strlen($parent->codcuenta)) {
                Tools::log()->warning('account-code-lower-than-parent', [
                    '%code%' => $this->codcuenta,
                    '%parent%' => $parent->codcuenta
                ]);
                return false;
            }
        }

        // code length must be lower than subaccounts
        if (strlen($this->codcuenta) >= $this->getExercise()->longsubcuenta) {
            Tools::log()->warning('account-code-bigger-than-subaccounts', ['%code%' => $this->codcuenta]);
            return false;
        }

        return parent::test();
    }

    public function url(string $type = 'auto', string $list = 'ListCuenta?activetab=List'): string
    {
        return parent::url($type, $list);
    }

    protected function fillToLength(int $length, string $value, string $prefix = ''): string
    {
        $value2 = trim($value);
        $count = $length - strlen($prefix) - strlen($value2);
        if ($count > 0) {
            return $prefix . str_repeat('0', $count) . $value2;
        } elseif ($count == 0) {
            return $prefix . $value2;
        }

        return '';
    }
}
