<?php

namespace App\Support;

use App\Enums\InvestmentStatus;
use App\Enums\InvestmentTransactionType;
use App\Models\Investment;
use App\Models\InvestmentTransaction;
use App\Models\User;
use Illuminate\Support\Collection;

class AdminDebugAudit
{
    public const PER_PAGE = 15;

    /**
     * @return array<string, mixed>
     */
    public static function paginateArray(array $items, int $page, int $perPage, string $routeName): array
    {
        $total = count($items);
        $lastPage = max(1, (int) ceil($total / $perPage));
        $page = max(1, min($page, $lastPage));
        $offset = ($page - 1) * $perPage;
        $slice = array_slice($items, $offset, $perPage);
        $from = $total > 0 ? $offset + 1 : null;
        $to = $total > 0 ? min($offset + $perPage, $total) : null;

        return [
            'data' => $slice,
            'current_page' => $page,
            'last_page' => $lastPage,
            'per_page' => $perPage,
            'total' => $total,
            'from' => $from,
            'to' => $to,
            'prev_page_url' => $page > 1 ? route($routeName, ['page' => $page - 1]) : null,
            'next_page_url' => $page < $lastPage ? route($routeName, ['page' => $page + 1]) : null,
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    public static function investmentBalanceInconsistencies(int $limit = 150): array
    {
        $issues = [];

        Investment::query()
            ->whereIn('status', [
                InvestmentStatus::Active->value,
                InvestmentStatus::Completed->value,
                InvestmentStatus::Pending->value,
            ])
            ->with(['user:id,first_name,last_name,email'])
            ->orderByDesc('id')
            ->lazyById(100)
            ->each(function (Investment $investment) use (&$issues, $limit): bool {
                if (count($issues) >= $limit) {
                    return false;
                }

                $stored = round((float) ($investment->capital_balance ?? $investment->amount), 2);
                $replay = InvestmentLedger::replayInvestmentTransactionBalances($investment);
                $computed = round($replay['final_capital'], 2);

                $capitalMismatch = abs($stored - $computed) >= 0.01;

                $txnMismatches = [];
                if ($replay['balances'] !== []) {
                    $txns = InvestmentTransaction::query()
                        ->where('investment_id', $investment->id)
                        ->whereIn('id', array_keys($replay['balances']))
                        ->get(['id', 'balance_after']);

                    foreach ($txns as $txn) {
                        $expected = $replay['balances'][(int) $txn->id] ?? null;
                        if ($expected === null) {
                            continue;
                        }

                        $actual = $txn->balance_after !== null
                            ? round((float) $txn->balance_after, 2)
                            : null;

                        if ($actual === null || abs($actual - $expected) >= 0.01) {
                            $txnMismatches[] = [
                                'transaction_id' => $txn->id,
                                'stored_usd' => $actual !== null
                                    ? number_format($actual, 2, '.', '')
                                    : null,
                                'expected_usd' => number_format($expected, 2, '.', ''),
                            ];
                        }
                    }
                }

                if (! $capitalMismatch) {
                    return true;
                }

                $user = $investment->user;
                $capitalDelta = round($computed - $stored, 2);
                $issues[] = [
                    'investment_id' => $investment->id,
                    'txid' => $investment->txid,
                    'status' => $investment->status instanceof InvestmentStatus
                        ? $investment->status->value
                        : (string) $investment->status,
                    'is_admin_adjustment' => InvestmentLedger::isAdminAdjustmentInvestment($investment),
                    'investor_name' => $user !== null
                        ? trim("{$user->first_name} {$user->last_name}")
                        : '—',
                    'investor_email' => $user?->email,
                    'stored_capital_usd' => number_format($stored, 2, '.', ''),
                    'computed_capital_usd' => number_format($computed, 2, '.', ''),
                    'capital_delta_usd' => number_format($capitalDelta, 2, '.', ''),
                    'transaction_mismatches' => $txnMismatches,
                ];

                return true;
            });

        return $issues;
    }

    /**
     * @return list<array<string, mixed>>
     */
    public static function balanceConsolidationCandidates(int $limit = 200): array
    {
        $rows = [];

        User::query()
            ->whereHas('investments', static function ($query): void {
                $query->whereIn('status', [
                    InvestmentStatus::Active->value,
                    InvestmentStatus::Completed->value,
                ]);
            })
            ->orderByDesc('id')
            ->lazyById(100)
            ->each(function (User $user) use (&$rows, $limit): bool {
                if (count($rows) >= $limit) {
                    return false;
                }

                $preview = InvestmentLedger::previewUserBalanceConsolidation($user->id);
                if (($preview['has_work'] ?? false) !== true) {
                    return true;
                }

                $amountToMove = round(
                    $preview['container_balance_after_usd'] - $preview['container_balance_before_usd'],
                    2,
                );

                $rows[] = [
                    'user_id' => $user->id,
                    'name' => trim("{$user->first_name} {$user->last_name}"),
                    'email' => $user->email,
                    'total_withdrawable_usd' => number_format($preview['total_withdrawable_usd'], 2, '.', ''),
                    'container_balance_before_usd' => number_format($preview['container_balance_before_usd'], 2, '.', ''),
                    'container_balance_after_usd' => number_format($preview['container_balance_after_usd'], 2, '.', ''),
                    'amount_to_move_usd' => number_format($amountToMove, 2, '.', ''),
                    'action_count' => count($preview['actions']),
                ];

                return true;
            });

        return $rows;
    }

    /**
     * @return array<string, mixed>
     */
    public static function paginatedBalanceConsolidationCandidates(int $page): array
    {
        $candidates = self::balanceConsolidationCandidates();

        $totalAmountToMove = round(array_sum(array_map(
            static fn (array $row): float => (float) $row['amount_to_move_usd'],
            $candidates,
        )), 2);

        return array_merge(
            self::paginateArray(
                $candidates,
                $page,
                self::PER_PAGE,
                'admin.debug.sections.balance-consolidation',
            ),
            [
                'total_amount_to_move_usd' => number_format($totalAmountToMove, 2, '.', ''),
            ],
        );
    }

    /**
     * @param  list<int>  $userIds
     * @return array{
     *     user_count: int,
     *     action_count: int,
     *     total_amount_to_move_usd: float,
     *     users: list<array{
     *         user_id: int,
     *         name: string,
     *         email: string,
     *         amount_to_move_usd: string,
     *         container_balance_before_usd: string,
     *         container_balance_after_usd: string,
     *         actions: list<array{
     *             investment_id: int,
     *             txid: string,
     *             amount_usd: string,
     *             description: string
     *         }>
     *     }>
     * }
     */
    public static function bulkBalanceConsolidationPreview(array $userIds): array
    {
        $users = [];
        $actionCount = 0;
        $totalMoved = 0.0;

        foreach (array_values(array_unique(array_map('intval', $userIds))) as $userId) {
            /** @var User|null $user */
            $user = User::query()->find($userId);
            if ($user === null) {
                continue;
            }

            $preview = InvestmentLedger::previewUserBalanceConsolidation($userId);
            if (($preview['has_work'] ?? false) !== true) {
                continue;
            }

            $amountToMove = round(
                $preview['container_balance_after_usd'] - $preview['container_balance_before_usd'],
                2,
            );
            $actions = array_map(static fn (array $action): array => [
                'investment_id' => $action['investment_id'],
                'txid' => $action['txid'],
                'amount_usd' => number_format($action['amount_usd'], 2, '.', ''),
                'description' => $action['description'],
            ], $preview['actions']);

            $actionCount += count($actions);
            $totalMoved = round($totalMoved + $amountToMove, 2);

            $users[] = [
                'user_id' => $user->id,
                'name' => trim("{$user->first_name} {$user->last_name}"),
                'email' => $user->email,
                'amount_to_move_usd' => number_format($amountToMove, 2, '.', ''),
                'container_balance_before_usd' => number_format($preview['container_balance_before_usd'], 2, '.', ''),
                'container_balance_after_usd' => number_format($preview['container_balance_after_usd'], 2, '.', ''),
                'actions' => $actions,
            ];
        }

        return [
            'user_count' => count($users),
            'action_count' => $actionCount,
            'total_amount_to_move_usd' => $totalMoved,
            'users' => $users,
        ];
    }

    /**
     * @param  list<int>  $userIds
     * @return array{processed: int, skipped: int, total_moved_usd: float}
     */
    public static function executeBulkBalanceConsolidation(array $userIds): array
    {
        $processed = 0;
        $skipped = 0;
        $totalMoved = 0.0;

        foreach (array_values(array_unique(array_map('intval', $userIds))) as $userId) {
            /** @var User|null $user */
            $user = User::query()->find($userId);
            if ($user === null) {
                $skipped++;

                continue;
            }

            $preview = InvestmentLedger::previewUserBalanceConsolidation($userId);
            if (($preview['has_work'] ?? false) !== true) {
                $skipped++;

                continue;
            }

            $result = InvestmentLedger::consolidateUserWithdrawableBalance($user);
            $processed++;
            $totalMoved = round($totalMoved + (float) $result['moved_total_usd'], 2);
        }

        return [
            'processed' => $processed,
            'skipped' => $skipped,
            'total_moved_usd' => $totalMoved,
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    public static function userWithdrawableInconsistencies(int $limit = 75): array
    {
        $issues = [];

        User::query()
            ->whereHas('investments', static function ($query): void {
                $query->whereIn('status', [
                    InvestmentStatus::Active->value,
                    InvestmentStatus::Completed->value,
                ]);
            })
            ->orderByDesc('id')
            ->lazyById(100)
            ->each(function (User $user) use (&$issues, $limit): bool {
                if (count($issues) >= $limit) {
                    return false;
                }

                $reported = round(InvestmentLedger::totalWithdrawableForUser($user->id), 2);

                $investments = Investment::query()
                    ->where('user_id', $user->id)
                    ->whereIn('status', [InvestmentStatus::Active, InvestmentStatus::Completed])
                    ->get();

                $sumParts = round($investments->sum(
                    static fn (Investment $inv): float => InvestmentLedger::withdrawableFromInvestment($inv),
                ), 2);

                if (abs($reported - $sumParts) < 0.01) {
                    return true;
                }

                $issues[] = [
                    'user_id' => $user->id,
                    'name' => trim("{$user->first_name} {$user->last_name}"),
                    'email' => $user->email,
                    'reported_withdrawable_usd' => number_format($reported, 2, '.', ''),
                    'sum_investment_withdrawable_usd' => number_format($sumParts, 2, '.', ''),
                    'delta_usd' => number_format(round($sumParts - $reported, 2), 2, '.', ''),
                ];

                return true;
            });

        return $issues;
    }

    /**
     * @return array<string, mixed>
     */
    public static function paginatedManualBalanceCredits(int $page): array
    {
        $perPage = self::PER_PAGE;

        $paginator = InvestmentTransaction::query()
            ->where('type', InvestmentTransactionType::AdminAdjustment)
            ->whereHas('investment', static function ($query): void {
                $query->where('txid', 'like', InvestmentLedger::ADMIN_CREDIT_TXID_PREFIX.'%');
            })
            ->with([
                'investment:id,user_id,txid,capital_balance',
                'investment.user:id,first_name,last_name,email',
                'createdBy:id,first_name,last_name,email',
            ])
            ->orderByDesc('id')
            ->paginate($perPage, ['*'], 'page', $page);

        $data = collect($paginator->items())
            ->map(static function (InvestmentTransaction $transaction): array {
                $user = $transaction->investment?->user;
                $containerBalance = (float) ($transaction->investment?->capital_balance ?? 0);
                $amount = (float) $transaction->amount;

                return [
                    'id' => $transaction->id,
                    'user_id' => $user?->id,
                    'investor_name' => $user !== null
                        ? trim("{$user->first_name} {$user->last_name}")
                        : '—',
                    'investor_email' => $user?->email,
                    'amount_usd' => number_format($amount, 2, '.', ''),
                    'balance_after_usd' => number_format((float) $transaction->balance_after, 2, '.', ''),
                    'container_balance_usd' => number_format($containerBalance, 2, '.', ''),
                    'admin_note' => $transaction->admin_note,
                    'created_at' => $transaction->created_at?->toIso8601String(),
                    'created_by' => $transaction->createdBy !== null
                        ? trim("{$transaction->createdBy->first_name} {$transaction->createdBy->last_name}")
                        : null,
                    'can_revert' => $containerBalance + 0.0001 >= $amount,
                ];
            })
            ->values()
            ->all();

        $revertableTotal = InvestmentTransaction::query()
            ->join('investments', 'investments.id', '=', 'investment_transactions.investment_id')
            ->where('investment_transactions.type', InvestmentTransactionType::AdminAdjustment)
            ->where('investments.txid', 'like', InvestmentLedger::ADMIN_CREDIT_TXID_PREFIX.'%')
            ->whereRaw('investments.capital_balance + 0.0001 >= investment_transactions.amount')
            ->count();

        return [
            'data' => $data,
            'current_page' => $paginator->currentPage(),
            'last_page' => $paginator->lastPage(),
            'per_page' => $paginator->perPage(),
            'total' => $paginator->total(),
            'from' => $paginator->firstItem(),
            'to' => $paginator->lastItem(),
            'prev_page_url' => $paginator->previousPageUrl(),
            'next_page_url' => $paginator->nextPageUrl(),
            'revertable_total' => $revertableTotal,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public static function paginatedPeriodAccrualInconsistencies(int $page): array
    {
        $report = self::periodAccrualInconsistencies();

        return array_merge(
            self::paginateArray(
                $report['items'],
                $page,
                self::PER_PAGE,
                'admin.debug.sections.accruals',
            ),
            [
                'scanned' => $report['scanned'],
                'truncated' => $report['truncated'],
                'total_period_accruals' => $report['total_period_accruals'],
            ],
        );
    }

    /**
     * @return list<array<string, mixed>>
     */
    public static function allManualBalanceCredits(): array
    {
        return InvestmentTransaction::query()
            ->where('type', InvestmentTransactionType::AdminAdjustment)
            ->whereHas('investment', static function ($query): void {
                $query->where('txid', 'like', InvestmentLedger::ADMIN_CREDIT_TXID_PREFIX.'%');
            })
            ->with([
                'investment:id,user_id,txid,capital_balance',
                'investment.user:id,first_name,last_name,email',
                'createdBy:id,first_name,last_name,email',
            ])
            ->orderByDesc('id')
            ->get()
            ->map(static function (InvestmentTransaction $transaction): array {
                $user = $transaction->investment?->user;
                $containerBalance = (float) ($transaction->investment?->capital_balance ?? 0);
                $amount = (float) $transaction->amount;

                return [
                    'id' => $transaction->id,
                    'user_id' => $user?->id,
                    'investor_name' => $user !== null
                        ? trim("{$user->first_name} {$user->last_name}")
                        : '—',
                    'investor_email' => $user?->email,
                    'amount_usd' => number_format($amount, 2, '.', ''),
                    'balance_after_usd' => number_format((float) $transaction->balance_after, 2, '.', ''),
                    'container_balance_usd' => number_format($containerBalance, 2, '.', ''),
                    'admin_note' => $transaction->admin_note,
                    'created_at' => $transaction->created_at?->toIso8601String(),
                    'created_by' => $transaction->createdBy !== null
                        ? trim("{$transaction->createdBy->first_name} {$transaction->createdBy->last_name}")
                        : null,
                    'can_revert' => $containerBalance + 0.0001 >= $amount,
                ];
            })
            ->values()
            ->all();
    }

    /**
     * @return array{items: list<array<string, mixed>>, scanned: int, truncated: bool}
     */
    public static function periodAccrualInconsistencies(int $limit = 120, int $maxScan = 400): array
    {
        $items = [];
        $scanned = 0;

        InvestmentTransaction::query()
            ->where('type', InvestmentTransactionType::PeriodAccrual)
            ->whereNull('beneficiary_user_id')
            ->with([
                'investment:id,user_id,txid,amount,monthly_return_snapshot,confirmed_at,maturity_at',
                'investment.user:id,first_name,last_name,email',
            ])
            ->orderByDesc('id')
            ->limit($maxScan)
            ->get()
            ->each(function (InvestmentTransaction $transaction) use (&$items, &$scanned, $limit): void {
                $scanned++;

                if ($transaction->investment === null || $transaction->accrual_on === null) {
                    return;
                }

                if (count($items) >= $limit) {
                    return;
                }

                try {
                    $preview = InvestmentLedger::previewPeriodAccrualRecalculation($transaction);
                } catch (\Throwable) {
                    return;
                }

                if (($preview['needs_change'] ?? false) !== true) {
                    return;
                }

                if (InvestmentLedger::periodAccrualIsIntentionalAdminRateExecution($transaction)) {
                    return;
                }

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

                $items[] = [
                    'transaction_id' => $transaction->id,
                    'investment_id' => $transaction->investment_id,
                    'accrual_on' => $transaction->accrual_on->toDateString(),
                    'txid' => $transaction->investment->txid,
                    'is_imported' => $preview['is_imported'] ?? false,
                    'investor_name' => $user !== null
                        ? trim("{$user->first_name} {$user->last_name}")
                        : '—',
                    'investor_email' => $user?->email,
                    'current_gain_usd' => $preview['current']['gain_usd'] ?? null,
                    'proposed_gain_usd' => $preview['proposed']['gain_usd'] ?? null,
                    'gain_delta_usd' => $preview['delta']['gain_usd'] ?? null,
                    'current_period' => self::formatPeriodRange(
                        $preview['current']['period_start'] ?? null,
                        $preview['current']['period_end'] ?? null,
                    ),
                    'proposed_period' => self::formatPeriodRange(
                        $preview['proposed']['period_start'] ?? null,
                        $preview['proposed']['period_end'] ?? null,
                    ),
                    'current_period_label' => $preview['current']['period_label'] ?? null,
                    'proposed_period_label' => $preview['proposed']['period_label'] ?? null,
                    'recalculate_preview_url' => route('admin.platform-accrual.recalculate-preview', $transaction),
                    'accrual_run_url' => route('admin.platform-accrual.show', [
                        'runDate' => $transaction->accrual_on->toDateString(),
                        'mode' => 'period',
                    ]),
                ];
            });

        $totalPeriodAccruals = InvestmentTransaction::query()
            ->where('type', InvestmentTransactionType::PeriodAccrual)
            ->whereNull('beneficiary_user_id')
            ->count();

        return [
            'items' => $items,
            'scanned' => $scanned,
            'truncated' => $totalPeriodAccruals > $maxScan || count($items) >= $limit,
            'total_period_accruals' => $totalPeriodAccruals,
        ];
    }

    /**
     * @return array<string, int>
     */
    public static function summary(
        array $investmentIssues,
        array $userIssues,
        array $manualCredits,
        array $accrualReport,
    ): array {
        $revertableManual = Collection::make($manualCredits)
            ->filter(static fn (array $row): bool => (bool) ($row['can_revert'] ?? false))
            ->count();

        return [
            'investment_balance_issues' => count($investmentIssues),
            'user_withdrawable_issues' => count($userIssues),
            'manual_balance_credits' => count($manualCredits),
            'manual_balance_revertable' => $revertableManual,
            'accrual_inconsistencies' => count($accrualReport['items'] ?? []),
        ];
    }

    private static function formatPeriodRange(?string $start, ?string $end): ?string
    {
        if ($start === null || $end === null) {
            return null;
        }

        return "{$start} → {$end}";
    }
}
