<?php

/**
 * Obtener el total de haberes: $disponibilidad->haberes.
 */
class Disponibilidad
{
    public $deuda;
    private $saldos;
    private $formula;
    private $ignorar;
    private $asociado;
    private $ordenFormula;
    private $bloqueo = null;
    private $proceso = null;
    private $porcentaje_disponibilidad = null;
    private $liquidacion;

    public function __construct($config)
    {
        Yii::import('application.modules.prestamo.models.DeudaActual');
        Yii::import('application.modules.configuracion.models.Formulas');
        Yii::import('application.modules.configuracion.models.FormulaProceso');
        $this->asociado = (new Warp($config))->get('asociado');
        $this->porcentaje_disponibilidad = (new Warp($config))->get('porcentaje_disponibilidad');
        $this->proceso = (new Warp($config))->get('proceso');
        $this->ignorar = (new Warp($config))->get('ignorar', []);

        if ($this->proceso == null) {
            throw new Exception('Se requiere la llave proceso \'proceso\'.');
        }

        if ($this->porcentaje_disponibilidad !== null && stripos($this->porcentaje_disponibilidad, '%') !== false) {
            throw new Exception("El porcentaje de disponibilidad debe definirse sin el caracter '%'.");
        }

        $this->deuda = new DeudaActual($this->asociado);
        $this->calculate();
    }

    public function formula($path = null)
    {
        return (new Warp($this->formula))->get($path);
    }

    private function calculate()
    {
        // H
        $reporteHaberes = $this->saldos();
        $items_base = $this->excluir('h', [
            1 => [
                'nombre' => 'Aporte del asociado',
                'monto' => $reporteHaberes->aporte_asociado,
            ],
            2 => [
                'nombre' => 'Aporte patronal',
                'monto' => $reporteHaberes->aporte_patrono,
            ],
            3 => [
                'nombre' => 'Aporte voluntario asociado',
                'monto' => $reporteHaberes->aporte_extra_socio,
            ],
            4 => [
                'nombre' => 'Aporte extraordinario del patrono',
                'monto' => $reporteHaberes->aporte_extra_patrono,
            ],
            5 => [
                'nombre' => 'Dividendos capitalizados',
                'monto' => $reporteHaberes->dividendos,
            ],
            6 => [
                'nombre' => 'Ajuste por asignación de haberes',
                'monto' => $reporteHaberes->totalAsignacion(),
            ],
            7 => [
                'nombre' => 'Ajuste por deducción de haberes',
                'estilos' => 'color: #a72828;',
                'monto' => $reporteHaberes->totalDeduccion(),
            ],
        ]);
        $items_total = $this->excluir('h', [
            8 => [
                'nombre' => 'Total haberes',
                'estilos' => 'font-weight: 900;',
                'monto' =>
                    (new Warp($items_base))->get('1.monto', 0) +
                    (new Warp($items_base))->get('2.monto', 0) +
                    (new Warp($items_base))->get('3.monto', 0) +
                    (new Warp($items_base))->get('4.monto', 0) +
                    (new Warp($items_base))->get('5.monto', 0) +
                    (new Warp($items_base))->get('6.monto', 0) -
                    (new Warp($items_base))->get('7.monto', 0),
            ],
        ]);

        // si la letra esta ignorada todos los montos se convierten en 0
        // solo si la formula personalizada esta habilitada
        $this->ignorar('H');

        $items = array_merge($items_base, $items_total);
        $this->formula['H'] = [
            'color' => '#428bca',
            'titulo' => 'Aportes',
            'id_tabla' => 'd_aportes',
            'id_detalle' => 'offer-w-aportes',
            'titulo_tabla' => 'Total haberes',
            'total' => (new Warp($items_total))->get('8.monto', 0),
            'items' => $items,
        ];

        // R
        list($formula, $porcentaje_disponibilidad) = $this->configuracionBasica();
        $retiro = null;
        if ($formula == 2 && $this->liquidacion() > 0) {
            $retiro = $this->excluir('r', [
                9 => [
                    'nombre' => 'Retiros parciales',
                    'monto' => $reporteHaberes->retiro_parcial,
                ],
            ]);
        } else {
            $this->formula['R'] = [
                'color' => '#5bc0de',
                'id_tabla' => 'd_retiros',
                'id_detalle' => 'offer-w-retiros',
                'titulo' => 'Retiros',
                'titulo_tabla' => 'Total retiros parciales',
                'items' => $this->excluir('r', [
                    9 => [
                        'estilos' => 'font-weight: 900;',
                        'nombre' => 'Total retiros parciales',
                        'monto' => $reporteHaberes->retiro_parcial,
                    ],
                ]),
            ];

            // si la letra esta ignorada todos los montos se convierten en 0
            // solo si la formula personalizada esta habilitada
            $this->ignorar('R');
            $this->formula['R'] = array_merge($this->formula['R'], [
                'total' => (new Warp($this->formula['R']['items']))->sum('monto') > 0
                        ? (new Warp($this->formula['R']['items']))->sum('monto')
                        : 0,
            ]);
        }

        // P
        $this->formula['P'] = [
            'color' => '#d9534f',
            'id_tabla' => 'd_deudas',
            'id_detalle' => 'offer-w-deudas',
            'titulo' => $this->deuda->hasBloqueo() ? 'Bloqueo de préstamos' : 'Deudas pendientes',
            'titulo_tabla' => $this->deuda->hasBloqueo() ? 'Bloqueo de préstamos' : 'Deudas pendientes',
            'items' => $this->excluir('p', [
                10 => [
                    'nombre' => 'Total bloqueo parcial préstamo',
                    'monto' => $this->deuda->bloqueo(),
                ],
                11 => [
                    'nombre' => 'Total bloqueo por garantia de haberes',
                    'monto' => $this->deuda->noBloqueo(),
                ],
                12 => [
                    'nombre' => 'Total bloqueado para solicitud',
                    'monto' => $this->deuda->total(),
                ],
                13 => [
                    'estilos' => 'font-weight: 900;',
                    'nombre' => 'Total préstamos con garantia de haberes',
                    'monto' => $this->deuda->total(),
                ],
            ]),
        ];

        // si la letra esta ignorada todos los montos se convierten en 0
        // solo si la formula personalizada esta habilitada
        $this->ignorar('P');

        $this->formula['P'] = array_merge($this->formula['P'], [
            'total' => (new Warp($this->formula))->get('P.items.12.monto', 0),
        ]);

        // X
        list($global, $temporal) = $this->bloqueo();
        $porcentaje_disponibilidad_base = $porcentaje_disponibilidad * 100;

        $total_haber_disponible = bcsub(
            $this->getDisponible(),
            $this->liquidacion() + (new Warp($retiro))->get('9.monto', 0) + $global + $temporal,
            2
        );

        $this->formula['X'] = [
            'color' => '#f0ad4e',
            'id_tabla' => 'd_haberes',
            'id_detalle' => 'offer-w-haberes',
            'titulo' => 'Haberes',
            'titulo_tabla' => 'Haber disponible',
            'items' => (new Warp(
                $this->excluir('x', [
                    14 => [
                        'estilos' => '',
                        'nombre' => 'Haber disponible',
                        'monto' => $this->getDisponible(),
                    ],
                    9 => (new Warp($retiro))->get('9', null),
                    19 =>
                        $this->liquidacion() > 0
                            ? [
                                'estilos' => '',
                                'nombre' => 'Liquidación de haberes',
                                'monto' => $this->liquidacion(),
                            ]
                            : null,
                    15 => [
                        'estilos' => '',
                        'nombre' => 'Haber bloqueado',
                        'monto' => $global,
                    ],
                    16 => [
                        'estilos' => '',
                        'nombre' => 'Bloqueo temporal por solicitudes',
                        'monto' => $temporal,
                    ],
                    17 => [
                        'estilos' => '',
                        'nombre' => 'Total haber disponible',
                        'monto' => $total_haber_disponible,
                    ],
                    18 => [
                        'estilos' => 'font-weight: 900;',
                        'nombre' => "{$porcentaje_disponibilidad_base}% de disponibilidad",
                        'monto' => $total_haber_disponible * $porcentaje_disponibilidad,
                    ],
                ])
            ))->filter(),
        ];

        // si la letra esta ignorada todos los montos se convierten en 0
        // solo si la formula personalizada esta habilitada
        $this->ignorar('X');
        if ($formula == 2 && $this->liquidacion() > 0) {
            $retiro = (new Warp($retiro))->get('9.monto', 0);
        } else {
            $retiro = 0;
        }

        $total_haber_disponible = bcsub(
            (new Warp($this->formula['X']))->get('items.14.monto', 0) +
                (new Warp($this->formula['X']))->get('items.15.monto', 0),
            (new Warp($this->formula['X']))->get('items.16.monto', 0) + $this->liquidacion() + $retiro,
            2
        );

        $this->formula['X'] = array_merge($this->formula['X'], [
            'total' => $total_haber_disponible * $porcentaje_disponibilidad,
        ]);

        // T
        $this->formula['T'] = [
            'color' => '#5cb85c',
            'id_tabla' => 'd_disponible',
            'id_detalle' => 'offer-w-disponible',
            'titulo' => 'Disponible para solicitud',
            'titulo_tabla' => 'Disponible para solicitud',
            'items' => [
                [
                    'estilos' => 'font-weight: 900;',
                    'nombre' => 'Total disponible para solicitud',
                    'monto' => $this->calcularFormula(),
                ],
            ],
            'total' => $this->calcularFormula(),
        ];

        // Ordenamiento
        $this->ordenFormula = array_merge($this->ordenFormula, ['T']);
        $this->ordenFormula = array_combine($this->ordenFormula, $this->ordenFormula);

        // Remueve las letras que estan siendo ignoradas
        $this->formula = array_filter(array_merge($this->ordenFormula, $this->formula), function ($items) {
            return count($items) > 1;
        });
    }

    private function calcularFormula()
    {
        if (empty($this->formula['t'])) {
            list(
                $id_formula,
                $porcentaje_disponibilidad,
                $formula_disponibilidad,
                $tipo_formula,
            ) = $this->configuracionBasica();

            $H = (new Warp($this->formula))->get('H.total', 0);
            $R = (new Warp($this->formula))->get('R.total', 0);
            $P = (new Warp($this->formula))->get('P.total', 0);

            if ($tipo_formula == 2) {
                // si la letra esta ignorada todas las variables pasan a ser 0
                if (in_array('H', $this->ignorar)) {
                    $H = 0;
                }

                if (in_array('R', $this->ignorar)) {
                    $R = 0;
                }

                if (in_array('P', $this->ignorar)) {
                    $P = 0;
                }
            }

            if ($id_formula == 2 && $this->liquidacion() > 0) {
                $retiro = (new Warp($this->formula))->get('X.items.9.monto', 0);
            } else {
                $retiro = 0;
            }

            $haberBloqueado = 0;
            $bloqueoTemporal = 0;

            if ($H > 0) {
                list($global, $temporal) = $this->bloqueo();
                $haberBloqueado = $global;
                $bloqueoTemporal = $temporal;
            }

            $busqueda = ['H', 'R', 'X', 'P'];
            $reemplazar = [
                bcsub($H, $this->liquidacion() + $retiro + $haberBloqueado + $bloqueoTemporal, 2),
                round($R,4),
                $porcentaje_disponibilidad,
                round($P,4)     ,
            ];
            $formulaBase = str_replace($busqueda, $reemplazar, $formula_disponibilidad);

            // Si el valor de P es negativo y se tiene una fórmula donde el valor de P es restado (- P), se evaluará de la
            // siguiente forma: --0.3. Lo que ocaciona que `eval` no pueda evaluar correctamente la expresión arrojando
            // error de sintaxis. Se añadiran espacios al rededor del operador -, para que `eval` pueda hacer la
            // correcta evaluación, obteniendo: - - 0.03.
            $formula = preg_replace(['/(?=.\+|\-|\*|\/)/', '/(?<=.\-)/'], ' ', $formulaBase);

            eval("\$res = ${formula};");
            $this->formula['T'] = round($res, 2);
        }

        return $this->formula['T'];
    }

    private function getDisponible()
    {
        $h = (new Warp($this->formula))->get('H.total');
        $r = (new Warp($this->formula))->get('R.total');
        list($formula) = $this->configuracionBasica();

        switch ($formula) {
            case 1:
                return $h - $r;
            case 2:
                return $h;
            case 3:
                return $h - $r - (new Warp($this->formula))->get('P.total');
        }
    }

    private function configuracionBasica()
    {
        $confBasica = Basica::model()->find();

        if ($this->porcentaje_disponibilidad == null) {
            $porcentaje = $confBasica->porcentaje_disponibilidad;
        } else {
            $porcentaje = $this->porcentaje_disponibilidad;
        }

        if ($porcentaje < 1) {
            $porcentaje = 80;
        }

        return [
            $confBasica->id_formula,
            $porcentaje / 100,
            $confBasica->formula_disponibilidad_haberes,
            $confBasica->id_tipo_formula,
        ];
    }

    private function saldos()
    {
        if (is_null($this->saldos)) {
            $this->saldos = ReporteHaberes::model()->find([
                'select' => 'idasociado,
                            aporte_total,
                            retiro_parcial,
                            aporte_asociado,
                            aporte_patrono,
                            aporte_extra_socio,
                            aporte_extra_patrono,
                            asignacion_monto_patrono,
                            asignacion_monto_asociado,
                            descuento_monto_asociado,
                            descuento_monto_patrono,
                            dividendos',
                'condition' => 'idasociado=:idasociado',
                'params' => [
                    'idasociado' => $this->asociado,
                ],
            ]);
        }

        return $this->saldos;
    }

    private function bloqueo()
    {
        if (is_null($this->bloqueo)) {
            $this->bloqueo = [$this->saldos()->haberBloqueado(), (new BloqueoTemporal($this->asociado))->run()];
        }

        return $this->bloqueo;
    }

    private function ignorar($letra)
    {
        list($id_formula, $_, $_, $tipo_formula) = $this->configuracionBasica();

        $this->ordenFormula = Formulas::formulaArray($id_formula);
        if ($tipo_formula == 1) {
            return;
        }

        if (in_array($letra, $this->ignorar)) {
            foreach ($this->formula[$letra]['items'] as &$item) {
                $item['monto'] = 0;
            }
        }
    }

    private function excluir($letra, $data = [])
    {
        list($id_formula, $_, $_, $tipo_formula) = $this->configuracionBasica();

        $this->ordenFormula = Formulas::formulaArray($id_formula);
        if ($tipo_formula == 1) {
            return $data;
        }
        $formulaPersonalizada = FormulaProceso::config([
            'proceso' => $this->proceso,
            'formula' => $id_formula,
        ]);

        $config = json_decode($formulaPersonalizada->configuracion);
        if (! array_key_exists($letra, $config)) {
            throw new Exception("La letra '{$letra}' no esta presente en la configuración.");
        }

        $letras = $config->{$letra};
        $letras = array_combine($letras, $letras);

        if ($letra == 'x' && $id_formula == 2 && $this->liquidacion() > 0) {
            $letras[9] = 9;
        }

        // flatMap
        $dataFiltrada = [];
        foreach ($data as $idLetra => $monto) {
            if (array_key_exists($idLetra, $letras)) {
                $dataFiltrada[$letras[$idLetra]] = $monto;
            }
        }

        return $dataFiltrada;
    }

    private function liquidacion()
    {
        if ($this->liquidacion == null) {
            $this->liquidacion = Yii::app()
                ->getDb()
                ->createCommand(
                    '
                select coalesce(sum(l.monto_total_disponible), 0) as total
                from retiro.liquidacion l
                inner join retiro.estatus_liquidacion el on el.id_liquidacion=l.id
                    and el.actual is true
                    and el.blnborrado is false
                    and el.id_estatus_liquidacion in (2,4)
                where l.blnborrado is false
                    and l.idasociado=:asociado
            '
                )
                ->bindValue('asociado', $this->asociado)
                ->queryRow()['total'];
        }

        return $this->liquidacion;
    }
}
