<?php

namespace App\Services\Finance;

use App\Models\Configuration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class StakeholderFinancialAnalysisService
{
    private array $excludedPaymentStatuses = [
        'void', 'voided', 'failed', 'refunded', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled',
    ];

    private array $excludedChargeStatuses = ['void', 'voided', 'cancelled', 'canceled'];

    public function analyze(?string $dateFrom, ?string $dateTo, ?string $schoolYear, ?string $compareSchoolYear = null): array
    {
        $schoolYear = $schoolYear ?: (Configuration::getConfig('school_year') ?? date('Y'));
        [$rangeFrom, $rangeTo] = $this->resolveDateRange($schoolYear, $dateFrom, $dateTo);

        $current = $this->periodMetrics($schoolYear, $rangeFrom, $rangeTo);
        $comparison = null;
        if (!empty($compareSchoolYear)) {
            [$compareFrom, $compareTo] = $this->resolveDateRange($compareSchoolYear, null, null);
            $comparison = $this->periodMetrics($compareSchoolYear, $compareFrom, $compareTo);
        }

        return [
            'schoolYear' => $schoolYear,
            'dateFrom' => $rangeFrom,
            'dateTo' => $rangeTo,
            'generatedAt' => now()->toISOString(),
            'audience' => 'stakeholders',
            'status' => 'read_only_report',
            'executiveSummary' => $this->executiveSummary($current, $comparison),
            'keyMetrics' => $current['keyMetrics'],
            'cashFlow' => $current['cashFlow'],
            'receivables' => $current['receivables'],
            'expenseAnalysis' => $current['expenseAnalysis'],
            'revenueAnalysis' => $current['revenueAnalysis'],
            'paymentMethodMix' => $current['paymentMethodMix'],
            'monthlyTrend' => $current['monthlyTrend'],
            'riskFlags' => $current['riskFlags'],
            'comparison' => $comparison ? [
                'schoolYear' => $compareSchoolYear,
                'keyMetrics' => $comparison['keyMetrics'],
                'deltas' => $this->deltas($current['keyMetrics'], $comparison['keyMetrics']),
            ] : null,
            'notes' => [
                'This report is additive and read-only. It does not mutate invoices, payments, refunds, expenses, reimbursements, or legacy PayPal records.',
                'Revenue is based on invoice and charge records; cash collection is based on non-voided payment records.',
                'Ratios are directional management indicators, not audited financial statements.',
            ],
        ];
    }

    public function csvRows(array $analysis): array
    {
        $rows = [];
        $rows[] = ['Section', 'Metric', 'Value'];
        foreach (($analysis['keyMetrics'] ?? []) as $key => $value) {
            $rows[] = ['Key Metrics', $key, is_scalar($value) ? $value : json_encode($value)];
        }
        foreach (($analysis['cashFlow'] ?? []) as $key => $value) {
            $rows[] = ['Cash Flow', $key, is_scalar($value) ? $value : json_encode($value)];
        }
        foreach (($analysis['receivables'] ?? []) as $key => $value) {
            if ($key === 'agingBuckets') {
                foreach ($value as $bucket => $amount) {
                    $rows[] = ['Receivables Aging', $bucket, $amount];
                }
                continue;
            }
            $rows[] = ['Receivables', $key, is_scalar($value) ? $value : json_encode($value)];
        }
        foreach (($analysis['expenseAnalysis']['byCategory'] ?? []) as $category => $amount) {
            $rows[] = ['Expense By Category', $category, $amount];
        }
        foreach (($analysis['paymentMethodMix'] ?? []) as $method => $amount) {
            $rows[] = ['Payment Method Mix', $method, $amount];
        }
        foreach (($analysis['monthlyTrend'] ?? []) as $month => $data) {
            $rows[] = ['Monthly Trend', $month . ' charges', $data['charges'] ?? 0];
            $rows[] = ['Monthly Trend', $month . ' collections', $data['collections'] ?? 0];
            $rows[] = ['Monthly Trend', $month . ' expenses', $data['expenses'] ?? 0];
            $rows[] = ['Monthly Trend', $month . ' netCash', $data['netCash'] ?? 0];
        }
        foreach (($analysis['riskFlags'] ?? []) as $flag) {
            $rows[] = ['Risk Flag', $flag['level'] ?? '', $flag['message'] ?? ''];
        }
        return $rows;
    }

    private function periodMetrics(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        $invoiceTotals = $this->invoiceTotals($schoolYear, $dateFrom, $dateTo);
        $payments = $this->paymentTotals($schoolYear, $dateFrom, $dateTo);
        $refunds = $this->refundTotals($schoolYear, $dateFrom, $dateTo);
        $discounts = $this->discountTotals($schoolYear, $dateFrom, $dateTo);
        $additionalCharges = $this->additionalChargeTotals($schoolYear, $dateFrom, $dateTo);
        $eventCharges = $this->eventChargeTotals($schoolYear, $dateFrom, $dateTo);
        $expenses = $this->expenseTotals($schoolYear, $dateFrom, $dateTo);
        $reimbursements = $this->reimbursementTotals($schoolYear, $dateFrom, $dateTo);

        $grossRevenue = $invoiceTotals['total'] + $additionalCharges['total'] + $eventCharges['total'];
        $netRevenue = max(0.0, $grossRevenue - $discounts['total'] - $refunds['total']);
        $cashCollected = $payments['total'];
        $cashOut = $expenses['total'] + $reimbursements['total'] + $refunds['total'];
        $netCash = $cashCollected - $cashOut;
        $receivables = max(0.0, $netRevenue - $cashCollected);
        $collectionRate = $netRevenue > 0 ? ($cashCollected / $netRevenue) * 100 : 0.0;
        $expenseRatio = $cashCollected > 0 ? (($expenses['total'] + $reimbursements['total']) / $cashCollected) * 100 : 0.0;
        $operatingMargin = $netRevenue > 0 ? (($netRevenue - $expenses['total'] - $reimbursements['total']) / $netRevenue) * 100 : 0.0;

        $aging = $this->receivableAging($schoolYear, $dateFrom, $dateTo);

        $keyMetrics = [
            'grossRevenue' => $this->money($grossRevenue),
            'netRevenue' => $this->money($netRevenue),
            'cashCollected' => $this->money($cashCollected),
            'refunds' => $this->money($refunds['total']),
            'discounts' => $this->money($discounts['total']),
            'expenses' => $this->money($expenses['total']),
            'reimbursements' => $this->money($reimbursements['total']),
            'netCash' => $this->money($netCash),
            'receivables' => $this->money($receivables),
            'collectionRatePct' => $this->percent($collectionRate),
            'expenseRatioPct' => $this->percent($expenseRatio),
            'operatingMarginPct' => $this->percent($operatingMargin),
            'invoiceCount' => $invoiceTotals['count'],
            'payingFamilyCount' => $payments['familyCount'],
        ];

        return [
            'keyMetrics' => $keyMetrics,
            'cashFlow' => [
                'cashIn' => $this->money($cashCollected),
                'cashOut' => $this->money($cashOut),
                'netCash' => $this->money($netCash),
                'cashOutBreakdown' => [
                    'expenses' => $this->money($expenses['total']),
                    'reimbursements' => $this->money($reimbursements['total']),
                    'refunds' => $this->money($refunds['total']),
                ],
            ],
            'receivables' => [
                'totalOpenReceivables' => $this->money($aging['total']),
                'agingBuckets' => array_map(fn ($v) => $this->money($v), $aging['buckets']),
                'largestOpenInvoices' => $aging['largestOpenInvoices'],
            ],
            'expenseAnalysis' => [
                'totalExpenses' => $this->money($expenses['total']),
                'byCategory' => array_map(fn ($v) => $this->money($v), $expenses['byCategory']),
            ],
            'revenueAnalysis' => [
                'invoiceCharges' => $this->money($invoiceTotals['total']),
                'additionalCharges' => $this->money($additionalCharges['total']),
                'eventCharges' => $this->money($eventCharges['total']),
                'discounts' => $this->money($discounts['total']),
                'refunds' => $this->money($refunds['total']),
                'netRevenue' => $this->money($netRevenue),
            ],
            'paymentMethodMix' => array_map(fn ($v) => $this->money($v), $payments['byMethod']),
            'monthlyTrend' => $this->monthlyTrend($schoolYear, $dateFrom, $dateTo),
            'riskFlags' => $this->riskFlags($collectionRate, $expenseRatio, $aging['buckets'], $netCash),
        ];
    }

    private function invoiceTotals(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        if (!Schema::hasTable('invoices')) {
            return ['total' => 0.0, 'count' => 0];
        }
        $q = DB::table('invoices')->where('school_year', $schoolYear);
        $this->applyDateRange($q, $this->dateColumn('invoices', ['issue_date', 'created_at']), $dateFrom, $dateTo);
        $row = (array) $q->selectRaw('COALESCE(SUM(total_amount),0) as total, COUNT(*) as count')->first();
        return ['total' => (float) ($row['total'] ?? 0), 'count' => (int) ($row['count'] ?? 0)];
    }

    private function paymentTotals(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        if (!Schema::hasTable('payments')) {
            return ['total' => 0.0, 'familyCount' => 0, 'byMethod' => []];
        }
        $base = DB::table('payments')->where('school_year', $schoolYear);
        $this->excludeStatuses($base, 'payments', $this->excludedPaymentStatuses);
        $this->applyDateRange($base, $this->dateColumn('payments', ['payment_date', 'created_at']), $dateFrom, $dateTo);
        $total = (float) ((array) (clone $base)->selectRaw('COALESCE(SUM(paid_amount),0) as total')->first())['total'];
        $familyCount = (int) ((array) (clone $base)->selectRaw('COUNT(DISTINCT parent_id) as count')->first())['count'];
        $methodRows = (clone $base)
            ->selectRaw("COALESCE(NULLIF(payment_method,''), 'unknown') as method, COALESCE(SUM(paid_amount),0) as total")
            ->groupBy('method')
            ->orderByDesc('total')
            ->get();
        $byMethod = [];
        foreach ($methodRows as $row) {
            $arr = (array) $row;
            $byMethod[(string) ($arr['method'] ?? 'unknown')] = (float) ($arr['total'] ?? 0);
        }
        return ['total' => $total, 'familyCount' => $familyCount, 'byMethod' => $byMethod];
    }

    private function refundTotals(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        if (!Schema::hasTable('refunds')) {
            return ['total' => 0.0];
        }
        $q = DB::table('refunds')->where('school_year', $schoolYear);
        if (Schema::hasColumn('refunds', 'status')) {
            $q->whereIn('status', ['paid', 'partially_paid', 'approved', 'Paid', 'Partially Paid', 'Approved']);
        }
        $this->applyDateRange($q, $this->dateColumn('refunds', ['refunded_at', 'approved_at', 'created_at']), $dateFrom, $dateTo);
        $amountColumn = Schema::hasColumn('refunds', 'refund_paid_amount') ? 'refund_paid_amount' : 'refund_amount';
        $row = (array) $q->selectRaw('COALESCE(SUM(' . $amountColumn . '),0) as total')->first();
        return ['total' => (float) ($row['total'] ?? 0)];
    }

    private function discountTotals(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        if (!Schema::hasTable('discount_usages')) {
            return ['total' => 0.0];
        }
        $q = DB::table('discount_usages as du');
        if (Schema::hasTable('invoices')) {
            $q->leftJoin('invoices as i', 'i.id', '=', 'du.invoice_id')->where('i.school_year', $schoolYear);
        }
        $this->excludeStatuses($q, 'discount_usages', $this->excludedChargeStatuses, 'du.status');
        $this->applyDateRange($q, Schema::hasColumn('discount_usages', 'created_at') ? 'du.created_at' : null, $dateFrom, $dateTo);
        $amountColumn = Schema::hasColumn('discount_usages', 'discount_amount') ? 'discount_amount' : 'amount';
        $row = (array) $q->selectRaw('COALESCE(SUM(du.' . $amountColumn . '),0) as total')->first();
        return ['total' => (float) ($row['total'] ?? 0)];
    }

    private function additionalChargeTotals(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        if (!Schema::hasTable('additional_charges')) {
            return ['total' => 0.0];
        }
        $q = DB::table('additional_charges')->where('school_year', $schoolYear);
        $this->excludeStatuses($q, 'additional_charges', $this->excludedChargeStatuses);
        $this->applyDateRange($q, $this->dateColumn('additional_charges', ['due_date', 'created_at']), $dateFrom, $dateTo);
        $row = (array) $q->selectRaw('COALESCE(SUM(amount),0) as total')->first();
        return ['total' => (float) ($row['total'] ?? 0)];
    }

    private function eventChargeTotals(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        $table = Schema::hasTable('event_charges') ? 'event_charges' : (Schema::hasTable('event_charge') ? 'event_charge' : null);
        if ($table === null) {
            return ['total' => 0.0];
        }
        $q = DB::table($table);
        if (Schema::hasColumn($table, 'school_year')) {
            $q->where('school_year', $schoolYear);
        }
        $this->excludeStatuses($q, $table, $this->excludedChargeStatuses);
        $amountColumn = Schema::hasColumn($table, 'amount') ? 'amount' : (Schema::hasColumn($table, 'charge_amount') ? 'charge_amount' : null);
        if ($amountColumn === null) {
            return ['total' => 0.0];
        }
        $this->applyDateRange($q, $this->dateColumn($table, ['charge_date', 'created_at']), $dateFrom, $dateTo);
        $row = (array) $q->selectRaw('COALESCE(SUM(' . $amountColumn . '),0) as total')->first();
        return ['total' => (float) ($row['total'] ?? 0)];
    }

    private function expenseTotals(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        if (!Schema::hasTable('expenses')) {
            return ['total' => 0.0, 'byCategory' => []];
        }
        $q = DB::table('expenses')->where('school_year', $schoolYear);
        $this->excludeStatuses($q, 'expenses', ['void', 'voided', 'rejected', 'cancelled', 'canceled']);
        $this->applyDateRange($q, $this->dateColumn('expenses', ['expense_date', 'date', 'created_at']), $dateFrom, $dateTo);
        $amountColumn = Schema::hasColumn('expenses', 'amount') ? 'amount' : 'total_amount';
        $total = (float) ((array) (clone $q)->selectRaw('COALESCE(SUM(' . $amountColumn . '),0) as total')->first())['total'];
        $categoryColumn = Schema::hasColumn('expenses', 'category') ? 'category' : (Schema::hasColumn('expenses', 'expense_category') ? 'expense_category' : null);
        $byCategory = [];
        if ($categoryColumn) {
            foreach ((clone $q)->selectRaw("COALESCE(NULLIF($categoryColumn,''), 'uncategorized') as category, COALESCE(SUM($amountColumn),0) as total")->groupBy('category')->orderByDesc('total')->get() as $row) {
                $arr = (array) $row;
                $byCategory[(string) ($arr['category'] ?? 'uncategorized')] = (float) ($arr['total'] ?? 0);
            }
        }
        return ['total' => $total, 'byCategory' => $byCategory];
    }

    private function reimbursementTotals(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        if (!Schema::hasTable('reimbursements')) {
            return ['total' => 0.0];
        }
        $q = DB::table('reimbursements')->where('school_year', $schoolYear);
        if (Schema::hasColumn('reimbursements', 'status')) {
            $q->whereNotIn('status', ['void', 'voided', 'rejected', 'cancelled', 'canceled']);
        }
        $this->applyDateRange($q, $this->dateColumn('reimbursements', ['reimbursed_at', 'created_at']), $dateFrom, $dateTo);
        $amountColumn = Schema::hasColumn('reimbursements', 'amount') ? 'amount' : (Schema::hasColumn('reimbursements', 'total_amount') ? 'total_amount' : 'reimbursement_amount');
        $row = (array) $q->selectRaw('COALESCE(SUM(' . $amountColumn . '),0) as total')->first();
        return ['total' => (float) ($row['total'] ?? 0)];
    }

    private function receivableAging(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        $buckets = ['current' => 0.0, '1_30_days' => 0.0, '31_60_days' => 0.0, '61_90_days' => 0.0, 'over_90_days' => 0.0];
        $largest = [];
        if (!Schema::hasTable('invoices')) {
            return ['total' => 0.0, 'buckets' => $buckets, 'largestOpenInvoices' => []];
        }
        $q = DB::table('invoices')->where('school_year', $schoolYear);
        $this->applyDateRange($q, $this->dateColumn('invoices', ['issue_date', 'created_at']), $dateFrom, $dateTo);
        $rows = $q->select(['id', 'invoice_number', 'parent_id', 'total_amount', 'paid_amount', 'balance', 'due_date', 'created_at'])->get();
        $today = now()->startOfDay();
        $total = 0.0;
        foreach ($rows as $row) {
            $arr = (array) $row;
            $balance = (float) ($arr['balance'] ?? 0);
            if ($balance <= 0) {
                $totalAmount = (float) ($arr['total_amount'] ?? 0);
                $paidAmount = (float) ($arr['paid_amount'] ?? 0);
                $balance = max(0.0, $totalAmount - $paidAmount);
            }
            if ($balance <= 0) {
                continue;
            }
            $total += $balance;
            $dueRaw = $arr['due_date'] ?? $arr['created_at'] ?? null;
            $days = 0;
            try {
                if (!empty($dueRaw)) {
                    $days = max(0, (int) \Illuminate\Support\Carbon::parse($dueRaw)->startOfDay()->diffInDays($today, false));
                }
            } catch (\Throwable $e) {
                $days = 0;
            }
            $bucket = $days <= 0 ? 'current' : ($days <= 30 ? '1_30_days' : ($days <= 60 ? '31_60_days' : ($days <= 90 ? '61_90_days' : 'over_90_days')));
            $buckets[$bucket] += $balance;
            $largest[] = [
                'invoiceId' => (int) ($arr['id'] ?? 0),
                'invoiceNumber' => (string) ($arr['invoice_number'] ?? ''),
                'parentId' => (int) ($arr['parent_id'] ?? 0),
                'balance' => $this->money($balance),
                'daysPastDue' => $days,
            ];
        }
        usort($largest, fn ($a, $b) => (float) $b['balance'] <=> (float) $a['balance']);
        return ['total' => $total, 'buckets' => $buckets, 'largestOpenInvoices' => array_slice($largest, 0, 10)];
    }

    private function monthlyTrend(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        $months = [];
        $invoiceMonth = $this->monthlySum('invoices', 'total_amount', $schoolYear, $this->dateColumn('invoices', ['issue_date', 'created_at']), $dateFrom, $dateTo);
        $paymentMonth = $this->monthlySum('payments', 'paid_amount', $schoolYear, $this->dateColumn('payments', ['payment_date', 'created_at']), $dateFrom, $dateTo, $this->excludedPaymentStatuses);
        $expenseColumn = Schema::hasTable('expenses') && Schema::hasColumn('expenses', 'amount') ? 'amount' : 'total_amount';
        $expenseMonth = $this->monthlySum('expenses', $expenseColumn, $schoolYear, $this->dateColumn('expenses', ['expense_date', 'date', 'created_at']), $dateFrom, $dateTo, ['void', 'voided', 'rejected', 'cancelled', 'canceled']);

        foreach (array_unique(array_merge(array_keys($invoiceMonth), array_keys($paymentMonth), array_keys($expenseMonth))) as $month) {
            $charges = $invoiceMonth[$month] ?? 0.0;
            $collections = $paymentMonth[$month] ?? 0.0;
            $expenses = $expenseMonth[$month] ?? 0.0;
            $months[$month] = [
                'charges' => $this->money($charges),
                'collections' => $this->money($collections),
                'expenses' => $this->money($expenses),
                'netCash' => $this->money($collections - $expenses),
            ];
        }
        ksort($months);
        return $months;
    }

    private function monthlySum(string $table, string $amountColumn, string $schoolYear, ?string $dateColumn, ?string $dateFrom, ?string $dateTo, array $excludedStatuses = []): array
    {
        if (!Schema::hasTable($table) || !Schema::hasColumn($table, $amountColumn) || $dateColumn === null) {
            return [];
        }
        $q = DB::table($table);
        if (Schema::hasColumn($table, 'school_year')) {
            $q->where('school_year', $schoolYear);
        }
        $this->excludeStatuses($q, $table, $excludedStatuses);
        $this->applyDateRange($q, $dateColumn, $dateFrom, $dateTo);
        $driver = DB::connection()->getDriverName();
        $monthExpr = $driver === 'sqlite'
            ? "strftime('%Y-%m', $dateColumn)"
            : "DATE_FORMAT($dateColumn, '%Y-%m')";
        $rows = $q->selectRaw($monthExpr . ' as month, COALESCE(SUM(' . $amountColumn . '),0) as total')
            ->groupBy('month')
            ->orderBy('month')
            ->get();
        $out = [];
        foreach ($rows as $row) {
            $arr = (array) $row;
            if (!empty($arr['month'])) {
                $out[(string) $arr['month']] = (float) ($arr['total'] ?? 0);
            }
        }
        return $out;
    }

    private function executiveSummary(array $current, ?array $comparison): array
    {
        $summary = [
            'headline' => 'Stakeholder financial analysis generated from existing Laravel finance tables.',
            'collectionRatePct' => $current['keyMetrics']['collectionRatePct'] ?? '0.00',
            'netCash' => $current['keyMetrics']['netCash'] ?? '0.00',
            'receivables' => $current['keyMetrics']['receivables'] ?? '0.00',
        ];
        if ($comparison) {
            $summary['comparison'] = $this->deltas($current['keyMetrics'], $comparison['keyMetrics']);
        }
        return $summary;
    }

    private function riskFlags(float $collectionRate, float $expenseRatio, array $agingBuckets, float $netCash): array
    {
        $flags = [];
        if ($collectionRate < 85.0) {
            $flags[] = ['level' => 'warning', 'message' => 'Collection rate is below 85%. Review receivables and follow-up cadence.'];
        }
        if (($agingBuckets['over_90_days'] ?? 0.0) > 0) {
            $flags[] = ['level' => 'warning', 'message' => 'There are receivables more than 90 days past due.'];
        }
        if ($expenseRatio > 80.0) {
            $flags[] = ['level' => 'watch', 'message' => 'Expenses and reimbursements exceed 80% of collections.'];
        }
        if ($netCash < 0) {
            $flags[] = ['level' => 'critical', 'message' => 'Net cash for the selected period is negative.'];
        }
        if (empty($flags)) {
            $flags[] = ['level' => 'ok', 'message' => 'No automatic risk flags were triggered for the selected period.'];
        }
        return $flags;
    }

    private function deltas(array $current, array $previous): array
    {
        $keys = ['grossRevenue', 'netRevenue', 'cashCollected', 'expenses', 'netCash', 'receivables'];
        $out = [];
        foreach ($keys as $key) {
            $cur = (float) ($current[$key] ?? 0);
            $prev = (float) ($previous[$key] ?? 0);
            $out[$key] = [
                'current' => $this->money($cur),
                'previous' => $this->money($prev),
                'change' => $this->money($cur - $prev),
                'changePct' => $prev != 0.0 ? $this->percent((($cur - $prev) / abs($prev)) * 100) : null,
            ];
        }
        return $out;
    }

    private function resolveDateRange(string $schoolYear, ?string $dateFrom, ?string $dateTo): array
    {
        if ((!$dateFrom || !$dateTo) && preg_match('/^(\d{4})\D?(\d{4})$/', $schoolYear, $m)) {
            $dateFrom = $dateFrom ?: $m[1] . '-01-01';
            $dateTo = $dateTo ?: $m[2] . '-12-31';
        }
        return [$dateFrom, $dateTo];
    }

    private function applyDateRange($query, ?string $column, ?string $dateFrom, ?string $dateTo): void
    {
        if ($column === null) {
            return;
        }
        if (!empty($dateFrom)) {
            $query->whereRaw('DATE(' . $column . ') >= ?', [$dateFrom]);
        }
        if (!empty($dateTo)) {
            $query->whereRaw('DATE(' . $column . ') <= ?', [$dateTo]);
        }
    }

    private function excludeStatuses($query, string $table, array $statuses, ?string $qualifiedColumn = null): void
    {
        if (empty($statuses) || !Schema::hasColumn($table, 'status')) {
            return;
        }
        $column = $qualifiedColumn ?: 'status';
        $query->where(function ($q) use ($column, $statuses) {
            $q->whereNotIn($column, $statuses)->orWhereNull($column);
        });
    }

    private function dateColumn(string $table, array $candidates): ?string
    {
        if (!Schema::hasTable($table)) {
            return null;
        }
        foreach ($candidates as $column) {
            if (Schema::hasColumn($table, $column)) {
                return $column;
            }
        }
        return null;
    }

    private function money(float $value): string
    {
        return number_format(round($value, 2), 2, '.', '');
    }

    private function percent(float $value): string
    {
        return number_format(round($value, 2), 2, '.', '');
    }
}
