<?php

namespace App\Http\Controllers\Admin;

use App\Enums\InvestmentTransactionType;
use App\Http\Controllers\Controller;
use App\Models\InvestmentSetting;
use App\Models\InvestmentTransaction;
use App\Support\InvestmentLedger;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;

class PlatformMonthlyAccrualController extends Controller
{
    /**
     * Ejecuciones automáticas agrupadas por fecha (diarias y por período).
     */
    public function index(): Response
    {
        $settings = InvestmentSetting::current();

        return Inertia::render('admin/platform-monthly-accrual', [
            'accrual' => [
                'frequency' => 'monthly',
                'day_of_month' => (int) $settings->accrual_day_of_month,
                'explain_daily' => 'Se registra una fila por inversión y día calendario. El comando programado ejecuta todos los días.',
                'explain_monthly' => 'Se registra una acreditación por período cada mes (día pactado del calendario, ajustando meses cortos).',
            ],
            'daily_run_summary' => $this->automaticRunSummariesForType(
                InvestmentTransactionType::DailyGain->value,
            ),
            'period_run_summary' => $this->automaticRunSummariesForType(
                InvestmentTransactionType::PeriodAccrual->value,
            ),
            'adjustments_enabled' => config('app.allow_adjustments'),
            'default_accrual_date' => now()->toDateString(),
        ]);
    }

    /**
     * Ejecuta acreditación sin confirmación por correo (solo ALLOW_ADJUSTMENTS).
     */
    public function runDebugAccrual(Request $request): RedirectResponse
    {
        abort_unless(config('app.allow_adjustments'), 404);

        $validated = $request->validate([
            'date' => ['nullable', 'date_format:Y-m-d'],
        ]);

        $on = isset($validated['date'])
            ? Carbon::parse($validated['date'])->startOfDay()
            : Carbon::today();

        $count = InvestmentLedger::runAccrualForDate($on);
        $finalized = InvestmentLedger::finalizeEndedActiveInvestments();

        $message = sprintf(
            'Acreditación de prueba (%s): %d operación(es) registrada(s).',
            $on->toDateString(),
            $count,
        );

        if ($finalized > 0) {
            $message .= sprintf(' Inversiones finalizadas: %d.', $finalized);
        }

        Inertia::flash('toast', [
            'type' => $count > 0 ? 'success' : 'info',
            'message' => $message,
        ]);

        return redirect()->route('admin.platform-accrual.index');
    }

    /**
     * Anula una ejecución de acreditación automática (solo ALLOW_ADJUSTMENTS).
     */
    public function voidAccrualRun(Request $request, string $runDate): RedirectResponse
    {
        abort_unless(config('app.allow_adjustments'), 404);

        $validated = $request->validate([
            'mode' => ['required', Rule::in(['daily', 'period'])],
        ]);

        $runDay = Carbon::parse($runDate)->startOfDay();

        try {
            $result = InvestmentLedger::voidAccrualRun($runDay, $validated['mode']);
        } catch (ValidationException $exception) {
            Inertia::flash('toast', [
                'type' => 'error',
                'message' => collect($exception->errors())->flatten()->first()
                    ?? 'No se pudo anular la ejecución.',
            ]);

            return redirect()->route('admin.platform-accrual.show', [
                'runDate' => $runDay->toDateString(),
                'mode' => $validated['mode'],
            ]);
        }

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => sprintf(
                'Ejecución anulada: %d ganancia(s) y %d comisión(es) de referidos eliminadas. Puede volver a ejecutar en modo debug.',
                $result['gain_count'],
                $result['referral_count'],
            ),
        ]);

        return redirect()->route('admin.platform-accrual.index');
    }

    /**
     * Vista previa del recálculo de una fila de acreditación por período (solo ALLOW_ADJUSTMENTS).
     */
    public function recalculatePreview(InvestmentTransaction $transaction): JsonResponse
    {
        abort_unless(config('app.allow_adjustments'), 404);

        if ($transaction->type !== InvestmentTransactionType::PeriodAccrual) {
            abort(404);
        }

        $transaction->load([
            'investment:id,user_id,txid,amount,monthly_return_snapshot,confirmed_at,maturity_at',
            'investment.user:id,first_name,last_name,email',
        ]);

        $investment = $transaction->investment;
        $user = $investment?->user;

        return response()->json([
            'investor_name' => $user !== null
                ? trim("{$user->first_name} {$user->last_name}")
                : '—',
            'investor_email' => $user?->email,
            ...InvestmentLedger::previewPeriodAccrualRecalculation($transaction),
        ]);
    }

    /**
     * Aplica recálculo de una acreditación por período (solo ALLOW_ADJUSTMENTS).
     */
    public function recalculateApply(InvestmentTransaction $transaction): RedirectResponse
    {
        abort_unless(config('app.allow_adjustments'), 404);

        if ($transaction->type !== InvestmentTransactionType::PeriodAccrual) {
            abort(404);
        }

        try {
            $result = InvestmentLedger::applyPeriodAccrualRecalculation($transaction);
        } catch (ValidationException $exception) {
            Inertia::flash('toast', [
                'type' => 'error',
                'message' => collect($exception->errors())->flatten()->first()
                    ?? 'No se pudo recalcular la acreditación.',
            ]);

            return back();
        }

        $gainDelta = $result['gain_delta'];
        $sign = $gainDelta >= 0 ? '+' : '';

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => sprintf(
                'Acreditación recalculada. Ganancia %s%s US$ · capital inversión %s%s US$.',
                $sign,
                number_format($gainDelta, 2, '.', ''),
                $result['investment_capital_delta'] >= 0 ? '+' : '',
                number_format($result['investment_capital_delta'], 2, '.', ''),
            ),
        ]);

        $runDate = $transaction->accrual_on?->toDateString();

        if ($runDate === null) {
            return redirect()->route('admin.platform-accrual.index');
        }

        return redirect()->route('admin.platform-accrual.show', [
            'runDate' => $runDate,
            'mode' => 'period',
        ]);
    }

    /**
     * Detalle de una ejecución (todas las filas del libro para esa fecha y modo).
     */
    public function show(Request $request, string $runDate): Response
    {
        $validated = $request->validate([
            'mode' => ['required', Rule::in(['daily', 'period'])],
        ]);

        $runDay = Carbon::parse($runDate)->startOfDay();
        $type = $validated['mode'] === 'daily'
            ? InvestmentTransactionType::DailyGain
            : InvestmentTransactionType::PeriodAccrual;

        $gainRows = InvestmentTransaction::query()
            ->whereNull('beneficiary_user_id')
            ->where('type', $type)
            ->whereDate('accrual_on', $runDay->toDateString())
            ->with([
                'investment:id,user_id,txid,amount,monthly_return_snapshot',
                'investment.user:id,first_name,last_name,email',
            ])
            ->orderBy('id')
            ->get();

        $sourceIds = $gainRows->pluck('id')->all();

        $referralRows = $sourceIds === []
            ? collect()
            : InvestmentTransaction::query()
                ->whereIn('referral_source_transaction_id', $sourceIds)
                ->with([
                    'beneficiary:id,first_name,last_name,email',
                    'investment:id,txid,user_id',
                ])
                ->orderBy('id')
                ->get();

        $totalGain = (float) $gainRows->sum('amount');
        $totalReferral = (float) $referralRows->sum('amount');

        return Inertia::render('admin/platform-monthly-accrual-detail', [
            'adjustments_enabled' => config('app.allow_adjustments'),
            'run' => [
                'date' => $runDay->toDateString(),
                'mode' => $validated['mode'],
                'mode_label' => $validated['mode'] === 'daily' ? 'Acreditación diaria' : 'Acreditación por período',
                'tx_count' => $gainRows->count(),
                'investments_distinct' => $gainRows->pluck('investment_id')->unique()->count(),
                'total_gain' => number_format($totalGain, 2, '.', ''),
                'referral_count' => $referralRows->count(),
                'total_referral' => number_format($totalReferral, 2, '.', ''),
            ],
            'lines' => $gainRows->map(static function (InvestmentTransaction $txn): array {
                $investment = $txn->investment;
                $user = $investment?->user;

                return [
                    'id' => $txn->id,
                    'investment_id' => $txn->investment_id,
                    'investor_name' => $user !== null
                        ? trim("{$user->first_name} {$user->last_name}")
                        : '—',
                    'investor_email' => $user?->email,
                    'txid' => $investment?->txid ?? '—',
                    'principal_usd' => $investment !== null
                        ? number_format((float) $investment->amount, 2, '.', '')
                        : null,
                    'plan_percent' => $investment !== null
                        ? number_format((float) $investment->monthly_return_snapshot, 2, '.', '')
                        : null,
                    'amount_usd' => number_format((float) $txn->amount, 2, '.', ''),
                    'balance_after_usd' => $txn->balance_after !== null
                        ? number_format((float) $txn->balance_after, 2, '.', '')
                        : null,
                    'applied_percent_label' => InvestmentLedger::appliedPercentLabel($txn),
                    'gain_period_start' => $txn->gain_period_start?->toDateString(),
                    'gain_period_end' => $txn->gain_period_end?->toDateString(),
                ];
            })->values()->all(),
            'referral_lines' => $referralRows->map(static function (InvestmentTransaction $txn): array {
                $beneficiary = $txn->beneficiary;

                return [
                    'id' => $txn->id,
                    'level' => $txn->referral_level,
                    'type' => $txn->type->value,
                    'beneficiary_name' => $beneficiary !== null
                        ? trim("{$beneficiary->first_name} {$beneficiary->last_name}")
                        : '—',
                    'beneficiary_email' => $beneficiary?->email,
                    'source_txid' => $txn->investment?->txid,
                    'amount_usd' => number_format((float) $txn->amount, 2, '.', ''),
                ];
            })->values()->all(),
        ]);
    }

    /**
     * @return Collection<int, array{run_date: string, tx_count: int, investments_distinct: int, total_gain: string}>
     */
    private function automaticRunSummariesForType(string $type): Collection
    {
        $driver = DB::connection()->getDriverName();
        $runDateExpr = match ($driver) {
            'sqlite' => 'date(accrual_on)',
            'pgsql' => 'CAST(accrual_on AS DATE)',
            default => 'DATE(accrual_on)',
        };

        return DB::table('investment_transactions')
            ->whereNull('beneficiary_user_id')
            ->where('type', $type)
            ->whereNotNull('accrual_on')
            ->selectRaw("{$runDateExpr} as run_date")
            ->selectRaw('COUNT(*) as tx_count')
            ->selectRaw('COUNT(DISTINCT investment_id) as investments_distinct')
            ->selectRaw('SUM(amount) as total_gain')
            ->groupBy(DB::raw($runDateExpr))
            ->orderByDesc(DB::raw($runDateExpr))
            ->limit(120)
            ->get()
            ->map(static function (\stdClass $row): array {
                return [
                    'run_date' => (string) $row->run_date,
                    'tx_count' => (int) $row->tx_count,
                    'investments_distinct' => (int) $row->investments_distinct,
                    'total_gain' => number_format((float) $row->total_gain, 2, '.', ''),
                ];
            });
    }
}
