<?php

namespace App\Helpers\Environment;

use Illuminate\Database\Eloquent\Builder;
use InvalidArgumentException;

use App\Helpers\Support\PeriodRangeDefiner;
use App\Models\Environment\Business;

class SystemLimitsHelper
{
    /**
     * Retorna la Cantidad limite de registros que una determinada entidad
     * tiene configurada
     *
     *  @param string $entity Session Key de la entidad a buscar
     *
     *  @return int|string Cantidad limite de registros permitida de la entidad en el sistema
    */
    public static function getEntityLimits($entity)
    {
        return session("limits.$entity", null);
    }

    /**
     * Valida si una determinada entidad ha alcanzado su limite de registros permitidos.
     * Deteniendo el proceso donde se mande a llamar en caso de ser así.
     *
     *  @param string   $model_class         Modelo de consulta para la Entidad
     *  @param string   $entity_session_key   Session key con la que la Entidad se encuentra registrada
     *  @param string   $module_section     Translate Support: Nombre del Módulo al que pertenece la entidad
     *  @param string   $entity_module_key    Translate Support: Nombre la entidad
     *  @param int      $exclude_quantity   Cantidad de registro a omitir, en caso de aplicar reglas especiales
     *
     *  @return void
     *  @throws 403 Mensaje de Limite alcanzado
    */
    public static function abortIfLimitReached(
            string $model_class,
            string $entity_session_key,
            string $module_section,
            string $entity_module_key,
            int $exclude_quantity = 0
        ): void
    {
        $limit = self::getEntityLimits($entity_session_key);
        if ($limit === null) return; // Sin límite

        $count = $model_class::count() - $exclude_quantity;
        if ($count >= $limit) {
            abort(response()->json([
                'limit_message' => str_replace(
                    [
                        ':entity',
                        ':limit'
                    ],
                    [
                        __('modules')['dashboard'][$module_section][$entity_module_key],
                        $limit
                    ],
                    __('common_responses_and_messages')['entity_limit_reached']
                )
            ], 403));
        }
    }

    /**
     * Valida si una determinada entidad ha alcanzado su limite de registros permitidos dentro de un determinado periodo de tiempo.
     * Deteniendo el proceso donde se mande a llamar en caso de ser así.
     *
     *  @param string   $model_class         Modelo de consulta para la Entidad
     *  @param string   $entity_session_key   Session key con la que la Entidad se encuentra registrada
     *  @param string   $module_section     Translate Support: Nombre del Módulo al que pertenece la entidad
     *  @param string   $entity_module_key    Translate Support: Nombre la entidad
     *  @param string   $period_frequency   Modelo bajo el cual se contabilizaran los registros 'daily' | 'weekly' | 'fortnightly' | 'monthly' | 'quarterly' | 'four_monthly' | 'yearly' ...
     *
     *  @param ?string      $period_column  Columna sobre la cual se aplicará la consulta de registros en el periodo indicado.
     *  @param ?callable    $extra_query    Callback opcional para agregar más filtros a la consulta principal.
     *
     *  @return void
     *  @throws 403 Mensaje de Limite alcanzado
    */
    public static function abortIfPeriodLimitReached(
      // General Identification
        string $model_class,
        string $entity_session_key,
        string $module_section, string $entity_module_key,

      // Query Complements
        string $period_frequency,
        ?string $period_column = 'created_at',
        ?callable $extra_query = null,
    ): void {
        // Consult and check Entity Limit
        $limit = self::getEntityLimits($entity_session_key);
            if ($limit === null) return; // Sin límite

        // Count how many records have been created in the indicated period
        [$start, $end] = PeriodRangeDefiner::range($period_frequency); // Define Period Range

        $query = $model_class::whereBetween($period_column, [$start, $end]);
            if ($extra_query !== null) {
                $result = $extra_query($query);

                if ($result instanceof Builder) $query = $result;
            }
        $count = $query->count();

        // Check if limit has been reached
        if ($count >= $limit) {
            abort(response()->json([
                'limit_message' => str_replace(
                    [
                        ':period_frequency',
                        ':entity',
                        ':limit'
                    ],
                    [
                        __('neutral_words')[$period_frequency],
                        __('modules')['dashboard'][$module_section][$entity_module_key],
                        $limit
                    ],
                    __('common_responses_and_messages')['periodic_entity_limit_reached']
                )
            ], 403));
        }
    }

    /**
     * Valida si una determinada entidad ha alcanzado su limite de registros permitidos
     * dentro de un determinado periodo de tiempo, considerando la suma de registros
     * provenientes de múltiples modelos (fuentes).
     *
     * Deteniendo el proceso donde se mande a llamar en caso de ser así.
     *
     *  @param string   $entity_session_key     Session key con la que la Entidad se encuentra registrada
     *  @param string   $module_section         Translate Support: Nombre del Módulo al que pertenece la entidad
     *  @param string   $entity_module_key      Translate Support: Nombre la entidad
     *  @param string   $period_frequency       Modelo bajo el cual se contabilizaran los registros 'daily' | 'weekly' | 'fortnightly' | 'monthly' | 'quarterly' | 'four_monthly' | 'yearly' ...
     *
     *  @param ?string  $period_column          Columna sobre la cual se aplicará la consulta de registros en el periodo indicado.
     *
     *  @param array    $sources                Fuentes de conteo. Cada fuente debe definir:
     *    [
     *      'model'       => string   (Modelo Eloquent),
     *      'column'      => ?string  (Columna de periodo opcional),
     *      'extra_query' => ?callable(Builder $query)
     *    ]
     *
     *  @return void|mixed
     *  @throws 403 Mensaje de Limite alcanzado
    */
    public static function abortIfPeriodLimitReachedSum(
      // General Identification
        string $entity_session_key,
        string $module_section,
        string $entity_module_key,

      // Query Complements
        string $period_frequency,
        string $period_column = 'created_at',

      // Breakers
        bool $just_compute = false,

        array $sources = [],
    ) {
        // Consult and check Entity Limit
        $limit = self::getEntityLimits($entity_session_key);
            if ($limit === null && !$just_compute) return; // Sin límite

        // Define Period Range
        [$start, $end] = PeriodRangeDefiner::range($period_frequency);

        // Initialize total counter
        $total = 0;

        // Iterate through all defined sources
        foreach ($sources as $source) {
            $model_class = $source['model'];
            $column = $source['column'] ?? $period_column;

            $query = $model_class::query()
                ->whereBetween($column, [$start, $end]);

            // Apply extra query filters if provided
            if (!empty($source['extra_query']) && is_callable($source['extra_query'])) {
                $result = $source['extra_query']($query);

                if ($result instanceof Builder) $query = $result;
            }

            // Accumulate total count
            $total += $query->count();
        }

        // Breakpoint: Just Return computed total
        if ($just_compute) return $total;

        // Check if limit has been reached
        if ($total >= $limit) {
            abort(response()->json([
                'limit_message' => str_replace(
                    [
                        ':period_frequency',
                        ':entity',
                        ':limit'
                    ],
                    [
                        __('neutral_words')[$period_frequency],
                        __('modules')['dashboard'][$module_section][$entity_module_key],
                        $limit
                    ],
                    __('common_responses_and_messages')['periodic_entity_limit_reached']
                ),
                'current_count' => $total,
                'limit' => $limit,
            ], 403));
        }
    }

    /*
      * Muestra de manera resumida el estatus actual los timbres ejecutados
      *
      * issued: Cantidad de timbres por paquete contratado
      * stamped: Cantidad de facturas/complementos timbrados
      * cancelled: Total de Facturas canceladas
      * used: Suma de las facturas, complementos y cancelaciones
      * available: Contador de facturas restantes puede usarse para mostrar mensajes pop up o estados
      * reached: Bandera de estado general (¿ya alcanzo el limite?)
    */
    public static function getStampingLimitStatus(): array{
        // Control Data
        $business = Business::first();
        $limits = $business->record_limits_configuration['invoices'];

        // Limit Control
        $issued = $limits['issued'] ?? null;
        $stamped = $limits['stamped'] ?? 0;
        $cancelled = $limits['cancelled'] ?? 0;

        $used = $stamped + $cancelled;

        // Return Limit Status
        return [
            'used' => $used,
            'issued' => $issued,
            'stamped' => $stamped,
            'cancelled' => $cancelled,
            'available' => $issued !== null ? max(0, $issued - $used) : null,
            'reached' => $issued !== null ?  $used >= $issued : false ,
        ];
    }

    // Verificación del límite actual y abortar ejecución si ya fue alcanzado
    public static function abortIfStampingLimitReached() : void{
        // Consult Stamping Limit
        $status = self::getStampingLimitStatus();
            if(!$status['reached']) return;

        // Abort Process if limit is reached
        abort(response()->json([
            'message' => __('common_responses_and_messages')['stamps_has_been_reached']
        ], 403));
    }

    // Aumenta el contador de sellos (facturas/complementos y cancelados)
    public static function incrementStampLimitCounter(string $type): void{
        // Define stamping action
        match ($type){
            'stamped', 'cancelled' => null,
            default => throw new InvalidArgumentException(
                "Invalid counter:  {$type}"
            ),
        };

        // Control Data
        $business = Business::first();
        $limits = $business->record_limits_configuration;

        // Update stamping counter
        $limits['invoices'][$type] = ($limits['invoices'][$type] ?? 0) + 1;
        $business->record_limits_configuration = $limits;
        $business->save();
    }
}
