<?php

namespace App\Services;

use App\Models\AccountingEntry;
use App\Models\AuditLog;
use App\Models\Branch;
use App\Models\Customer;
use App\Models\FinancialAccountTransaction;
use App\Models\Loan;
use App\Models\ManagementAlert;
use App\Models\User;
use Carbon\CarbonImmutable;
use Illuminate\Support\Collection;

class ManagementDashboardService
{
    private const COLLECTION_TYPES = ['loan_payment', 'loan_early_settlement', 'loan_write_off_recovery'];
    private const DISBURSEMENT_TYPES = ['loan_disbursement', 'loan_top_up'];
    private const INCOME_CATEGORIES = ['interest', 'fees', 'penalties', 'write_off_recoveries', 'service_income', 'other_income', 'settlement_adjustment'];
    private const EXPENSE_CATEGORIES = ['expenses', 'payroll', 'write_offs', 'depreciation_expense', 'asset_maintenance', 'settlement_adjustment', 'other_expenses'];

    public function __construct(
        private FinancialReportService $financialReports,
        private LoanBranchReportService $loanReports,
        private MoneyMath $money,
    ) {}

    public function generate(array $branchIds, string $from, string $to): array
    {
        $today = CarbonImmutable::now()->toDateString();
        $monthStart = CarbonImmutable::now()->startOfMonth()->toDateString();
        $allTimeFrom = '1900-01-01';

        $collections = $this->financialReports->generate('collections', $branchIds, $from, $to);
        $disbursements = $this->financialReports->generate('disbursements', $branchIds, $from, $to);
        $expenses = $this->financialReports->generate('expenses', $branchIds, $from, $to);
        $periodPnl = $this->financialReports->generate('profit-loss', $branchIds, $from, $to);
        $monthPnl = $this->financialReports->generate('profit-loss', $branchIds, $monthStart, $today);
        $accounts = $this->financialReports->generate('account-statement', $branchIds, $allTimeFrom, $today);

        $active = $this->loanReports->generate('active-loan-portfolio', $branchIds, $from, $to);
        $principal = $this->loanReports->generate('outstanding-principal', $branchIds, $from, $to);
        $interest = $this->loanReports->generate('outstanding-interest', $branchIds, $from, $to);
        $overdue = $this->loanReports->generate('overdue-loans', $branchIds, $from, $to);
        $completed = $this->loanReports->generate('completed-loans', $branchIds, $allTimeFrom, $today);
        $writtenOff = $this->loanReports->generate('written-off-loans', $branchIds, $allTimeFrom, $today);
        $collectionRate = $this->loanReports->generate('loan-collection-rate', $branchIds, $from, $to);
        $branchPerformance = $this->loanReports->generate('branch-performance', $branchIds, $from, $to);

        $availableFundsMinor = 0;
        $accountTypes = ['cash' => 0, 'mobile_money' => 0, 'bank' => 0, 'other' => 0];
        foreach ($accounts['summaries'] as $summary) {
            $minor = $this->money->toSignedMinor((string) $summary['closing_balance']);
            $availableFundsMinor += $minor;
            $type = $summary['account']->account_type;
            $group = in_array($type, ['cash', 'mobile_money', 'bank'], true) ? $type : 'other';
            $accountTypes[$group] += $minor;
        }

        $flowSeries = $this->flowSeries($branchIds, $from, $to);
        $incomeExpenseSeries = $this->incomeExpenseSeries($branchIds, $from, $to);
        $branchRows = $this->chartBranchRows($branchPerformance['rows']);
        $systemOverview = $this->systemOverview($branchIds, $from, $to);
        $loanStatusDistribution = $this->loanStatusDistribution($branchIds);
        $managementAlerts = $this->managementAlerts($branchIds);
        $recentActivity = $this->recentActivity($branchIds);

        $totalOutstanding = $this->summaryValue($active, 'Total outstanding');
        $overdueAmount = $this->summaryValue($overdue, 'Total overdue');

        return [
            'metrics' => [
                'available_funds' => $this->money->fromSignedMinor($availableFundsMinor),
                'outstanding_principal' => $this->summaryValue($principal, 'Outstanding principal'),
                'expected_interest' => $this->summaryValue($interest, 'Outstanding interest'),
                'collections' => $collections['total_collections'],
                'disbursements' => $disbursements['total_disbursements'],
                'expenses' => $expenses['total_expenses'],
                'overdue_amount' => $overdueAmount,
                'period_profit' => $periodPnl['net_profit'],
                'profit_this_month' => $monthPnl['net_profit'],
                'active_loans' => $this->summaryValue($active, 'Active loans'),
                'completed_loans' => $this->summaryValue($completed, 'Completed loans'),
                'written_off_portfolio' => $this->summaryValue($writtenOff, 'Recoverable balance'),
            ],
            'system_overview' => $systemOverview,
            'account_balances' => [
                'cash' => $this->money->fromSignedMinor($accountTypes['cash']),
                'mobile_money' => $this->money->fromSignedMinor($accountTypes['mobile_money']),
                'bank' => $this->money->fromSignedMinor($accountTypes['bank']),
                'other' => $this->money->fromSignedMinor($accountTypes['other']),
            ],
            'account_summaries' => $accounts['summaries'],
            'flow_series' => $flowSeries,
            'income_expense_series' => $incomeExpenseSeries,
            'loan_status_distribution' => $loanStatusDistribution,
            'management_alerts' => $managementAlerts,
            'recent_activity' => $recentActivity,
            'portfolio_performance' => [
                'total_outstanding' => $totalOutstanding,
                'overdue' => $overdueAmount,
                'scheduled_due' => $this->summaryValue($collectionRate, 'Scheduled due'),
                'collected' => $this->summaryValue($collectionRate, 'Collected'),
                'collection_rate' => $this->summaryValue($collectionRate, 'Collection rate'),
                'portfolio_at_risk' => $this->ratioPercent($overdueAmount, $totalOutstanding),
            ],
            'branch_performance' => $branchRows,
            'period_pnl' => $periodPnl,
            'reconciliation' => [
                'financial_reports' => [
                    'collections' => $collections['total_collections'],
                    'disbursements' => $disbursements['total_disbursements'],
                    'expenses' => $expenses['total_expenses'],
                    'period_profit' => $periodPnl['net_profit'],
                ],
                'loan_reports' => [
                    'active_loans' => $this->summaryValue($active, 'Active loans'),
                    'outstanding_principal' => $this->summaryValue($principal, 'Outstanding principal'),
                    'outstanding_interest' => $this->summaryValue($interest, 'Outstanding interest'),
                    'overdue_amount' => $overdueAmount,
                    'collection_rate' => $this->summaryValue($collectionRate, 'Collection rate'),
                ],
            ],
        ];
    }

    private function systemOverview(array $branchIds, string $from, string $to): array
    {
        $fromDate = CarbonImmutable::parse($from)->startOfDay();
        $toDate = CarbonImmutable::parse($to)->endOfDay();
        $pendingStatuses = [
            'application_submitted',
            'waiting_for_documents',
            'contract_generated',
            'contract_signed',
            'under_review',
            'correction_requested',
            'additional_collateral_requested',
        ];

        return [
            'branches' => Branch::query()->whereIn('id', $branchIds)->count(),
            'active_branches' => Branch::query()->whereIn('id', $branchIds)->where('status', 'active')->count(),
            'customers' => Customer::query()->whereIn('branch_id', $branchIds)->count(),
            'active_customers' => Customer::query()->whereIn('branch_id', $branchIds)->where('status', 'active')->count(),
            'new_customers' => Customer::query()->whereIn('branch_id', $branchIds)->whereBetween('created_at', [$fromDate, $toDate])->count(),
            'staff' => User::query()->whereHas('branches', fn ($query) => $query->whereIn('branches.id', $branchIds))->count(),
            'loans' => Loan::query()->whereIn('branch_id', $branchIds)->count(),
            'pending_approvals' => Loan::query()->whereIn('branch_id', $branchIds)->whereIn('status', $pendingStatuses)->count(),
            'ready_for_disbursement' => Loan::query()->whereIn('branch_id', $branchIds)->where('status', 'ready_for_disbursement')->count(),
            'overdue_loans' => Loan::query()->whereIn('branch_id', $branchIds)->where('status', 'active')->where('total_overdue', '>', 0)->count(),
            'open_alerts' => ManagementAlert::query()->whereIn('branch_id', $branchIds)->where('status', 'open')->count(),
        ];
    }

    private function loanStatusDistribution(array $branchIds): array
    {
        $statusCounts = Loan::query()
            ->whereIn('branch_id', $branchIds)
            ->selectRaw('status, COUNT(*) as total')
            ->groupBy('status')
            ->pluck('total', 'status')
            ->map(fn ($count) => (int) $count);

        $groups = [
            ['key' => 'pending', 'label' => 'Pending application', 'statuses' => ['draft', 'application_submitted', 'waiting_for_documents', 'contract_generated', 'contract_signed', 'under_review', 'correction_requested', 'additional_collateral_requested']],
            ['key' => 'ready', 'label' => 'Ready to disburse', 'statuses' => ['ready_for_disbursement']],
            ['key' => 'active', 'label' => 'Active', 'statuses' => ['active']],
            ['key' => 'completed', 'label' => 'Completed', 'statuses' => ['completed', 'fully_paid', 'closed_through_top_up']],
            ['key' => 'written_off', 'label' => 'Written off', 'statuses' => ['written_off']],
            ['key' => 'rejected', 'label' => 'Rejected', 'statuses' => ['rejected']],
        ];

        $rows = array_map(function (array $group) use ($statusCounts): array {
            $count = collect($group['statuses'])->sum(fn (string $status) => (int) $statusCounts->get($status, 0));

            return [
                'key' => $group['key'],
                'label' => $group['label'],
                'count' => $count,
            ];
        }, $groups);

        $total = max(1, array_sum(array_column($rows, 'count')));
        $rows = array_map(fn (array $row) => array_merge($row, [
            'percentage' => (int) round(($row['count'] / $total) * 100),
        ]), $rows);

        return [
            'total' => array_sum(array_column($rows, 'count')),
            'rows' => $rows,
        ];
    }

    private function managementAlerts(array $branchIds): array
    {
        $items = ManagementAlert::query()
            ->with('branch:id,name')
            ->whereIn('branch_id', $branchIds)
            ->whereIn('status', ['open', 'acknowledged'])
            ->orderByDesc('occurred_at')
            ->orderByDesc('id')
            ->limit(6)
            ->get()
            ->map(fn (ManagementAlert $alert) => [
                'id' => $alert->id,
                'title' => $alert->title,
                'message' => $alert->message,
                'type' => $alert->type,
                'severity' => $alert->severity,
                'status' => $alert->status,
                'branch' => $alert->branch?->name,
                'occurred_at' => $alert->occurred_at,
            ])
            ->all();

        return [
            'open' => ManagementAlert::query()->whereIn('branch_id', $branchIds)->where('status', 'open')->count(),
            'acknowledged' => ManagementAlert::query()->whereIn('branch_id', $branchIds)->where('status', 'acknowledged')->count(),
            'items' => $items,
        ];
    }

    private function recentActivity(array $branchIds): array
    {
        return AuditLog::query()
            ->with(['user:id,name', 'branch:id,name'])
            ->whereIn('branch_id', $branchIds)
            ->orderByDesc('created_at')
            ->orderByDesc('id')
            ->limit(8)
            ->get()
            ->map(fn (AuditLog $log) => [
                'id' => $log->id,
                'action' => str($log->action)->replace('_', ' ')->headline()->toString(),
                'subject' => class_basename((string) $log->auditable_type),
                'subject_id' => $log->auditable_id,
                'user' => $log->user?->name ?? 'System',
                'branch' => $log->branch?->name,
                'created_at' => $log->created_at,
            ])
            ->all();
    }

    private function flowSeries(array $branchIds, string $from, string $to): array
    {
        $fromDate = CarbonImmutable::parse($from)->startOfDay();
        $toDate = CarbonImmutable::parse($to)->endOfDay();
        $transactions = FinancialAccountTransaction::query()
            ->whereIn('branch_id', $branchIds)
            ->whereBetween('transaction_date', [$fromDate, $toDate])
            ->whereIn('type', array_values(array_unique(array_merge(self::COLLECTION_TYPES, self::DISBURSEMENT_TYPES))))
            ->orderBy('transaction_date')
            ->get(['transaction_date', 'type', 'money_in', 'money_out']);

        $rows = [];
        foreach ($this->buckets($fromDate, $toDate) as $bucket) {
            $items = $transactions->filter(fn ($transaction) => $transaction->transaction_date->betweenIncluded($bucket['start'], $bucket['end']));
            $collections = $this->transactionMinor($items->whereIn('type', self::COLLECTION_TYPES), 'money_in');
            $disbursements = $this->transactionMinor($items->whereIn('type', self::DISBURSEMENT_TYPES), 'money_out');
            $rows[] = [
                'label' => $bucket['label'],
                'collections' => $this->money->fromMinor($collections),
                'disbursements' => $this->money->fromMinor($disbursements),
                'collections_minor' => $collections,
                'disbursements_minor' => $disbursements,
            ];
        }

        $max = max(1, collect($rows)->max(fn (array $row) => max($row['collections_minor'], $row['disbursements_minor'])) ?? 1);

        return array_map(fn (array $row) => array_merge($row, [
            'collections_pct' => (int) round(($row['collections_minor'] / $max) * 100),
            'disbursements_pct' => (int) round(($row['disbursements_minor'] / $max) * 100),
        ]), $rows);
    }

    private function incomeExpenseSeries(array $branchIds, string $from, string $to): array
    {
        $fromDate = CarbonImmutable::parse($from)->startOfDay();
        $toDate = CarbonImmutable::parse($to)->endOfDay();
        $entries = AccountingEntry::query()
            ->whereIn('branch_id', $branchIds)
            ->whereBetween('entry_date', [$fromDate, $toDate])
            ->whereIn('category', array_values(array_unique(array_merge(self::INCOME_CATEGORIES, self::EXPENSE_CATEGORIES))))
            ->orderBy('entry_date')
            ->get(['entry_date', 'category', 'debit', 'credit']);

        $rows = [];
        foreach ($this->buckets($fromDate, $toDate) as $bucket) {
            $items = $entries->filter(fn ($entry) => CarbonImmutable::parse($entry->entry_date)->betweenIncluded($bucket['start'], $bucket['end']));
            $income = $this->entryMinor($items->whereIn('category', self::INCOME_CATEGORIES), 'credit');
            $expense = $this->entryMinor($items->whereIn('category', self::EXPENSE_CATEGORIES), 'debit');
            $rows[] = [
                'label' => $bucket['label'],
                'income' => $this->money->fromMinor($income),
                'expenses' => $this->money->fromMinor($expense),
                'income_minor' => $income,
                'expenses_minor' => $expense,
            ];
        }

        $max = max(1, collect($rows)->max(fn (array $row) => max($row['income_minor'], $row['expenses_minor'])) ?? 1);

        return array_map(fn (array $row) => array_merge($row, [
            'income_pct' => (int) round(($row['income_minor'] / $max) * 100),
            'expenses_pct' => (int) round(($row['expenses_minor'] / $max) * 100),
        ]), $rows);
    }

    private function chartBranchRows(array $rows): array
    {
        $max = 1;
        foreach ($rows as $row) {
            foreach (['collections', 'disbursed_amount', 'total_outstanding'] as $field) {
                $max = max($max, $this->money->toMinor((string) ($row[$field] ?? '0.00')));
            }
        }

        return array_map(function (array $row) use ($max): array {
            return array_merge($row, [
                'collections_pct' => (int) round(($this->money->toMinor((string) ($row['collections'] ?? '0.00')) / $max) * 100),
                'disbursed_pct' => (int) round(($this->money->toMinor((string) ($row['disbursed_amount'] ?? '0.00')) / $max) * 100),
                'portfolio_pct' => (int) round(($this->money->toMinor((string) ($row['total_outstanding'] ?? '0.00')) / $max) * 100),
            ]);
        }, $rows);
    }

    private function buckets(CarbonImmutable $from, CarbonImmutable $to): array
    {
        $days = $from->startOfDay()->diffInDays($to->startOfDay()) + 1;
        $buckets = [];

        if ($days <= 31) {
            for ($cursor = $from->startOfDay(); $cursor->lte($to); $cursor = $cursor->addDay()) {
                $buckets[] = ['start' => $cursor, 'end' => $cursor->endOfDay(), 'label' => $cursor->format('d M')];
            }

            return $buckets;
        }

        if ($days <= 180) {
            for ($cursor = $from->startOfDay(); $cursor->lte($to); $cursor = $cursor->addDays(7)) {
                $end = $cursor->addDays(6)->endOfDay();
                if ($end->gt($to)) {
                    $end = $to;
                }
                $buckets[] = ['start' => $cursor, 'end' => $end, 'label' => $cursor->format('d M')];
            }

            return $buckets;
        }

        for ($cursor = $from->startOfMonth(); $cursor->lte($to); $cursor = $cursor->addMonth()->startOfMonth()) {
            $start = $cursor->lt($from) ? $from : $cursor;
            $end = $cursor->endOfMonth()->gt($to) ? $to : $cursor->endOfMonth();
            $buckets[] = ['start' => $start, 'end' => $end, 'label' => $cursor->format('M Y')];
        }

        return $buckets;
    }

    private function summaryValue(array $report, string $label): string
    {
        $row = collect($report['summary'] ?? [])->firstWhere('label', $label);

        return (string) ($row['value'] ?? '0.00');
    }

    private function ratioPercent(string $numerator, string $denominator): string
    {
        $denominatorMinor = $this->money->toMinor($denominator);
        if ($denominatorMinor <= 0) {
            return '0.00';
        }

        $numeratorMinor = $this->money->toMinor($numerator);

        return number_format(($numeratorMinor / $denominatorMinor) * 100, 2, '.', '');
    }

    private function transactionMinor(Collection $items, string $field): int
    {
        $total = 0;
        foreach ($items as $item) {
            $total += $this->money->toMinor((string) $item->{$field});
        }

        return $total;
    }

    private function entryMinor(Collection $items, string $field): int
    {
        $total = 0;
        foreach ($items as $item) {
            $total += $this->money->toMinor((string) $item->{$field});
        }

        return $total;
    }
}
