<?php

namespace App\Services;

use App\Models\ActivityLog;
use App\Models\BillPayment;
use App\Models\Customer;
use App\Models\GatewayPayment;
use App\Models\InvestmentAccount;
use App\Models\InvestmentTransaction;
use App\Models\Loan;
use App\Models\LoanPayment;
use App\Models\MemberTransfer;
use App\Models\SavingsAccount;
use App\Models\SavingsTransaction;
use App\Models\SupportTicket;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class MemberAccountService
{
    /**
     * @return array<string, mixed>
     */
    public function overview(Customer $customer): array
    {
        $customer->loadMissing([
            'savingsAccounts.transactions',
            'investmentAccounts.transactions',
            'loans.product',
            'loans.schedules',
            'loans.payments',
        ]);

        $openSchedules = $customer->loans
            ->flatMap(fn (Loan $loan) => $loan->schedules)
            ->whereIn('status', ['pending', 'partial', 'overdue'])
            ->sortBy('due_date')
            ->values();

        $nextSchedule = $openSchedules->first();
        $dueToday = $openSchedules
            ->filter(fn ($schedule) => $schedule->due_date && $schedule->due_date->lte(today()))
            ->sum(fn ($schedule) => max(0, (float) $schedule->total_due - (float) $schedule->paid_amount));

        return [
            'savings_balance' => $customer->savingsAccounts->sum(fn (SavingsAccount $account) => (float) $account->balance),
            'loan_balance' => $customer->loans
                ->whereIn('status', ['active', 'disbursed'])
                ->sum(fn (Loan $loan) => (float) $loan->balance),
            'pending_loan_count' => $customer->loans->whereIn('status', ['applied', 'under_review', 'approved'])->count(),
            'due_amount' => $dueToday,
            'next_due_date' => $nextSchedule?->due_date,
            'next_due_amount' => $nextSchedule ? max(0, (float) $nextSchedule->total_due - (float) $nextSchedule->paid_amount) : 0,
            'transactions' => $this->transactions($customer),
            'audit_logs' => $this->auditTrail($customer),
            'investment' => $this->investmentSummary($customer),
        ];
    }

    public function transactions(Customer $customer, int $limit = 100): Collection
    {
        $rows = collect();

        SavingsTransaction::with('account')
            ->where('customer_id', $customer->id)
            ->whereNotIn('method', ['internal_transfer', 'bank_transfer'])
            ->latest('transacted_at')
            ->limit($limit)
            ->get()
            ->each(function (SavingsTransaction $transaction) use ($rows): void {
                $rows->push([
                    'date' => $transaction->transacted_at,
                    'sort' => $this->sortDate($transaction->transacted_at),
                    'category' => 'Savings '.ucfirst($transaction->type),
                    'reference' => $transaction->reference,
                    'method' => $transaction->method,
                    'description' => $transaction->description ?: $transaction->account?->product_name,
                    'amount' => (float) $transaction->amount,
                    'direction' => $transaction->type === 'withdrawal' ? 'debit' : 'credit',
                    'balance' => (float) $transaction->balance_after,
                    'status' => $transaction->status ?? 'posted',
                ]);
            });

        LoanPayment::with('loan')
            ->where('customer_id', $customer->id)
            ->latest('paid_at')
            ->limit($limit)
            ->get()
            ->each(function (LoanPayment $payment) use ($rows): void {
                $rows->push([
                    'date' => $payment->paid_at,
                    'sort' => $this->sortDate($payment->paid_at),
                    'category' => 'Loan Repayment',
                    'reference' => $payment->reference,
                    'method' => $payment->method,
                    'description' => $payment->notes ?: ($payment->loan?->loan_number ?? 'Loan repayment'),
                    'amount' => (float) $payment->amount,
                    'direction' => 'debit',
                    'balance' => (float) $payment->balance_after,
                    'status' => $payment->status ?? LoanPayment::STATUS_POSTED,
                ]);
            });

        Loan::where('customer_id', $customer->id)
            ->whereIn('status', ['active', 'disbursed', 'completed'])
            ->whereNotNull('disbursed_at')
            ->latest('disbursed_at')
            ->limit($limit)
            ->get()
            ->each(function (Loan $loan) use ($rows): void {
                $rows->push([
                    'date' => $loan->disbursed_at,
                    'sort' => $this->sortDate($loan->disbursed_at),
                    'category' => 'Loan Disbursement',
                    'reference' => $loan->loan_number,
                    'method' => 'loan',
                    'description' => $loan->purpose ?: 'Loan funds released',
                    'amount' => (float) ($loan->amount_disbursed ?: $loan->principal),
                    'direction' => 'credit',
                    'balance' => (float) $loan->balance,
                    'status' => $loan->status,
                ]);
            });

        GatewayPayment::where('customer_id', $customer->id)
            ->latest()
            ->limit($limit)
            ->get()
            ->each(function (GatewayPayment $payment) use ($rows): void {
                $rows->push([
                    'date' => $payment->paid_at ?? $payment->created_at,
                    'sort' => $this->sortDate($payment->paid_at ?? $payment->created_at),
                    'category' => 'Online '.str_replace('_', ' ', ucfirst($payment->purpose)),
                    'reference' => $payment->reference,
                    'method' => $payment->provider,
                    'description' => $payment->provider_reference ? 'Gateway reference '.$payment->provider_reference : 'Gateway checkout',
                    'amount' => (float) $payment->amount,
                    'direction' => $payment->purpose === GatewayPayment::PURPOSE_SAVINGS_DEPOSIT ? 'credit' : 'debit',
                    'balance' => null,
                    'status' => $payment->status,
                ]);
            });

        BillPayment::where('customer_id', $customer->id)
            ->latest()
            ->limit($limit)
            ->get()
            ->each(function (BillPayment $payment) use ($rows): void {
                $rows->push([
                    'date' => $payment->processed_at ?? $payment->created_at,
                    'sort' => $this->sortDate($payment->processed_at ?? $payment->created_at),
                    'category' => 'Bill Payment',
                    'reference' => $payment->reference,
                    'method' => $payment->payment_method,
                    'description' => ucfirst(str_replace('_', ' ', $payment->service_type)).' - '.$payment->customer_identifier,
                    'amount' => (float) $payment->total_amount,
                    'direction' => 'debit',
                    'balance' => null,
                    'status' => $payment->status,
                ]);
            });

        if (Schema::hasTable('member_transfers')) {
            MemberTransfer::with(['sender', 'sourceAccount', 'recipient', 'recipientAccount'])
                ->where(function ($query) use ($customer): void {
                    $query->where('sender_customer_id', $customer->id)
                        ->orWhere('recipient_customer_id', $customer->id);
                })
                ->latest()
                ->limit($limit)
                ->get()
                ->each(function (MemberTransfer $transfer) use ($rows, $customer): void {
                    if ((int) $transfer->sender_customer_id === (int) $customer->id) {
                        $rows->push([
                            'date' => $transfer->posted_at ?? $transfer->created_at,
                            'sort' => $this->sortDate($transfer->posted_at ?? $transfer->created_at),
                            'category' => $transfer->transfer_type === MemberTransfer::TYPE_BANK ? 'Bank Transfer' : 'Transfer Out',
                            'reference' => $transfer->reference,
                            'method' => $transfer->transfer_type,
                            'description' => $transfer->recipient_bank_name
                                ? $transfer->recipient_bank_name.' - '.$transfer->recipient_account_number
                                : 'To '.($transfer->recipient?->fullName() ?: $transfer->recipient_account_number),
                            'amount' => (float) $transfer->total_amount,
                            'direction' => 'debit',
                            'balance' => null,
                            'status' => $transfer->status,
                        ]);
                    }

                    if ((int) $transfer->recipient_customer_id === (int) $customer->id) {
                        $rows->push([
                            'date' => $transfer->posted_at ?? $transfer->created_at,
                            'sort' => $this->sortDate($transfer->posted_at ?? $transfer->created_at),
                            'category' => 'Transfer In',
                            'reference' => $transfer->reference,
                            'method' => $transfer->transfer_type,
                            'description' => 'From '.($transfer->sender?->fullName() ?: 'Member'),
                            'amount' => (float) $transfer->amount,
                            'direction' => 'credit',
                            'balance' => null,
                            'status' => $transfer->status,
                        ]);
                    }
                });
        }

        if (Schema::hasTable('investment_transactions')) {
            DB::table('investment_transactions')
                ->where('customer_id', $customer->id)
                ->latest('created_at')
                ->limit($limit)
                ->get()
                ->each(function ($transaction) use ($rows): void {
                    $type = (string) ($transaction->type ?? 'investment');
                    $rows->push([
                        'date' => Carbon::parse($transaction->created_at),
                        'sort' => $this->sortDate($transaction->created_at),
                        'category' => 'Investment '.ucfirst($type),
                        'reference' => $transaction->reference ?? '-',
                        'method' => $transaction->method ?? 'investment',
                        'description' => $transaction->description ?? 'Investment transaction',
                        'amount' => (float) ($transaction->amount ?? 0),
                        'direction' => in_array($type, ['withdrawal', 'payout', 'return'], true) ? 'credit' : 'debit',
                        'balance' => isset($transaction->balance_after) ? (float) $transaction->balance_after : null,
                        'status' => $transaction->status ?? 'posted',
                    ]);
                });
        }

        return $rows->sortByDesc('sort')->take($limit)->values();
    }

    public function auditTrail(Customer $customer, int $limit = 80): Collection
    {
        $subjects = [
            Customer::class => [$customer->id],
            SavingsAccount::class => $customer->savingsAccounts()->pluck('id')->all(),
            SavingsTransaction::class => SavingsTransaction::where('customer_id', $customer->id)->pluck('id')->all(),
            InvestmentAccount::class => Schema::hasTable('investment_accounts') ? InvestmentAccount::where('customer_id', $customer->id)->pluck('id')->all() : [],
            InvestmentTransaction::class => Schema::hasTable('investment_transactions') ? InvestmentTransaction::where('customer_id', $customer->id)->pluck('id')->all() : [],
            Loan::class => $customer->loans()->pluck('id')->all(),
            LoanPayment::class => LoanPayment::where('customer_id', $customer->id)->pluck('id')->all(),
            GatewayPayment::class => GatewayPayment::where('customer_id', $customer->id)->pluck('id')->all(),
            BillPayment::class => BillPayment::where('customer_id', $customer->id)->pluck('id')->all(),
            MemberTransfer::class => Schema::hasTable('member_transfers')
                ? MemberTransfer::where(function ($query) use ($customer): void {
                    $query->where('sender_customer_id', $customer->id)
                        ->orWhere('recipient_customer_id', $customer->id);
                })->pluck('id')->all()
                : [],
            SupportTicket::class => SupportTicket::where('user_id', $customer->user_id)->pluck('id')->all(),
        ];

        return ActivityLog::with('user')
            ->where('tenant_id', $customer->tenant_id)
            ->where(function ($query) use ($subjects): void {
                foreach ($subjects as $type => $ids) {
                    if (empty($ids)) {
                        continue;
                    }

                    $query->orWhere(function ($subjectQuery) use ($type, $ids): void {
                        $subjectQuery->where('subject_type', $type)->whereIn('subject_id', $ids);
                    });
                }
            })
            ->latest()
            ->limit($limit)
            ->get();
    }

    /**
     * @return array<string, mixed>
     */
    private function investmentSummary(Customer $customer): array
    {
        if (! Schema::hasTable('investment_accounts')) {
            return [
                'ready' => false,
                'accounts' => collect(),
                'transactions' => collect(),
                'balance' => 0,
            ];
        }

        $accounts = DB::table('investment_accounts')
            ->where('tenant_id', $customer->tenant_id)
            ->where('customer_id', $customer->id)
            ->latest()
            ->get();

        $transactions = Schema::hasTable('investment_transactions')
            ? DB::table('investment_transactions')
                ->where('tenant_id', $customer->tenant_id)
                ->where('customer_id', $customer->id)
                ->latest()
                ->limit(20)
                ->get()
            : collect();

        return [
            'ready' => true,
            'accounts' => $accounts,
            'transactions' => $transactions,
            'balance' => $accounts->sum(fn ($account) => (float) ($account->balance ?? $account->principal ?? 0)),
        ];
    }

    private function sortDate(mixed $date): int
    {
        if ($date instanceof Carbon) {
            return $date->getTimestamp();
        }

        return $date ? Carbon::parse($date)->getTimestamp() : 0;
    }
}
