<?php

namespace App\Http\Controllers\Tenant;

use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\InvestmentAccount;
use App\Models\InvestmentProduct;
use App\Models\InvestmentTransaction;
use App\Services\ActivityLogger;
use App\Services\MemberNotificationAutomationService;
use App\Services\NumberGenerator;
use App\Services\TransactionCancellationService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use InvalidArgumentException;

class InvestmentController extends Controller
{
    public function index(): View
    {
        $user = auth()->user();
        $tenant = $user->tenant;

        if ($tenant && ! InvestmentProduct::where('tenant_id', $tenant->id)->exists()) {
            InvestmentProduct::createDefaultsForTenant($tenant);
        }

        $products = InvestmentProduct::orderBy('name')->get();

        return view('tenant.investments.index', [
            'activeCustomers' => Customer::visibleTo($user)->where('status', 'active')->count(),
            'customers' => Customer::visibleTo($user)->where('status', 'active')->orderBy('first_name')->get(),
            'products' => $products,
            'accounts' => InvestmentAccount::with(['customer.accountOfficer', 'product'])
                ->whereHas('customer', fn ($query) => $query->visibleTo($user))
                ->latest()
                ->paginate(15),
            'pendingInvestmentDeposits' => InvestmentTransaction::with(['customer', 'account', 'creator', 'approver'])
                ->whereHas('account.customer', fn ($query) => $query->visibleTo($user))
                ->where('type', 'deposit')
                ->where('status', 'pending')
                ->latest('transacted_at')
                ->limit(10)
                ->get(),
            'totalInvested' => InvestmentAccount::whereHas('customer', fn ($query) => $query->visibleTo($user))->where('status', 'active')->sum('amount'),
            'totalBalance' => InvestmentAccount::whereHas('customer', fn ($query) => $query->visibleTo($user))->where('status', 'active')->sum('balance'),
            'pendingApproval' => InvestmentTransaction::whereHas('account.customer', fn ($query) => $query->visibleTo($user))
                ->where('type', 'deposit')
                ->where('status', 'pending')
                ->count(),
            'maturingSoon' => InvestmentAccount::whereHas('customer', fn ($query) => $query->visibleTo($user))
                ->where('status', 'active')
                ->whereBetween('maturity_date', [today(), today()->addDays(30)])
                ->count(),
            'termUnits' => InvestmentProduct::TERM_UNITS,
            'returnTypes' => InvestmentProduct::RETURN_TYPES,
            'currency' => $tenant->settings['currency'] ?? 'NGN',
        ]);
    }

    public function show(InvestmentAccount $investment): View
    {
        $investment->load(['customer', 'product', 'approver', 'transactions' => fn ($query) => $query->with(['creator', 'approver', 'canceler'])->latest('transacted_at')]);
        abort_unless($investment->customer?->isVisibleTo(auth()->user()), 403, 'This member is assigned to another account officer.');

        return view('tenant.investments.show', [
            'account' => $investment,
            'currency' => auth()->user()->tenant->settings['currency'] ?? 'NGN',
            'progress' => $this->progress($investment),
        ]);
    }

    public function storeProduct(Request $request): RedirectResponse
    {
        $product = InvestmentProduct::create($this->validatedProduct($request));

        ActivityLogger::log('investment_product_created', 'Investment product '.$product->name.' created.', $product);

        return back()->with('status', 'Investment product created.');
    }

    public function updateProduct(Request $request, InvestmentProduct $product): RedirectResponse
    {
        $product->update($this->validatedProduct($request, $product));

        ActivityLogger::log('investment_product_updated', 'Investment product '.$product->name.' updated.', $product);

        return back()->with('status', 'Investment product updated.');
    }

    public function destroyProduct(InvestmentProduct $product): RedirectResponse
    {
        if (! $product->accounts()->exists()) {
            $name = $product->name;
            $product->delete();

            ActivityLogger::log('investment_product_deleted', 'Investment product '.$name.' deleted.');

            return back()->with('status', 'Investment product deleted.');
        }

        $product->update(['is_active' => false, 'allow_member_request' => false]);
        ActivityLogger::log('investment_product_disabled', 'Investment product '.$product->name.' disabled because accounts already use it.', $product);

        return back()->with('status', 'Investment product disabled because accounts already use it.');
    }

    public function storeAccount(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'customer_id' => ['required', 'exists:customers,id'],
            'investment_product_id' => ['required', 'exists:investment_products,id'],
            'amount' => ['required', 'numeric', 'min:0.01'],
            'start_date' => ['nullable', 'date'],
            'auto_rollover' => ['nullable', 'boolean'],
            'notes' => ['nullable', 'string', 'max:1000'],
        ]);

        $customer = Customer::visibleTo($request->user())
            ->where('status', 'active')
            ->findOrFail($validated['customer_id']);
        $product = InvestmentProduct::where('is_active', true)->findOrFail($validated['investment_product_id']);
        $amount = (float) $validated['amount'];

        if ((float) $product->minimum_amount > 0 && $amount < (float) $product->minimum_amount) {
            return back()->with('warning', 'Investment amount is below the product minimum.')->withInput();
        }

        if ((float) $product->maximum_amount > 0 && $amount > (float) $product->maximum_amount) {
            return back()->with('warning', 'Investment amount exceeds the product maximum.')->withInput();
        }

        $startDate = Carbon::parse($validated['start_date'] ?? today());
        $maturityDate = $this->maturityDate($startDate->copy(), (int) $product->term_value, $product->term_unit);
        $account = DB::transaction(function () use ($request, $customer, $product, $validated, $amount, $startDate, $maturityDate): InvestmentAccount {
            $account = InvestmentAccount::create([
                'tenant_id' => $customer->tenant_id,
                'customer_id' => $customer->id,
                'investment_product_id' => $product->id,
                'account_number' => NumberGenerator::make($customer->tenant, $product->investment_number_prefix ?: 'INV', InvestmentAccount::class, 'account_number'),
                'product_name' => $product->name,
                'amount' => 0,
                'balance' => 0,
                'expected_return' => 0,
                'return_paid' => 0,
                'interest_rate' => $product->interest_rate,
                'term_value' => $product->term_value,
                'term_unit' => $product->term_unit,
                'start_date' => $startDate->toDateString(),
                'maturity_date' => $maturityDate->toDateString(),
                'status' => 'active',
                'approved_by' => $request->user()->id,
                'approved_at' => now(),
                'auto_rollover' => $request->boolean('auto_rollover'),
                'notes' => $validated['notes'] ?? null,
                'opened_at' => now(),
            ]);

            InvestmentTransaction::create([
                'tenant_id' => $account->tenant_id,
                'investment_account_id' => $account->id,
                'customer_id' => $account->customer_id,
                'created_by' => $request->user()->id,
                'type' => 'deposit',
                'amount' => $amount,
                'balance_before' => 0,
                'balance_after' => 0,
                'reference' => NumberGenerator::reference('INVDEP'),
                'method' => 'cash',
                'status' => 'pending',
                'description' => 'Initial investment deposit awaiting approval',
                'transacted_at' => now(),
            ]);

            return $account;
        });

        ActivityLogger::log('investment_deposit_pending', 'Investment '.$account->account_number.' opened with a pending deposit for '.$customer->fullName().'.', $account);

        return redirect()->route('tenant.investments.show', $account)->with('status', 'Investment opened. Deposit is waiting for approval.');
    }

    public function approve(InvestmentAccount $investment): RedirectResponse
    {
        $investment->load('customer');
        abort_unless($investment->customer?->isVisibleTo(auth()->user()), 403, 'This member is assigned to another account officer.');
        abort_unless($investment->status === 'pending', 422);

        DB::transaction(function () use ($investment): void {
            $account = InvestmentAccount::whereKey($investment->id)->lockForUpdate()->firstOrFail();

            $account->update([
                'status' => 'active',
                'balance' => $account->amount,
                'approved_by' => auth()->id(),
                'approved_at' => now(),
                'rejected_at' => null,
                'rejected_reason' => null,
                'opened_at' => $account->opened_at ?: now(),
            ]);

            if (! $account->transactions()->exists()) {
                InvestmentTransaction::create([
                    'tenant_id' => $account->tenant_id,
                    'investment_account_id' => $account->id,
                    'customer_id' => $account->customer_id,
                    'created_by' => auth()->id(),
                    'type' => 'deposit',
                    'amount' => $account->amount,
                    'balance_before' => 0,
                    'balance_after' => $account->amount,
                    'reference' => NumberGenerator::reference('INVDEP'),
                    'method' => 'cash',
                    'status' => 'posted',
                    'approved_by' => auth()->id(),
                    'approved_at' => now(),
                    'description' => 'Initial investment deposit on approval',
                    'transacted_at' => now(),
                ]);
            }
        });

        ActivityLogger::log('investment_account_approved', 'Investment '.$investment->account_number.' approved.', $investment);

        return back()->with('status', 'Investment approved.');
    }

    public function reject(Request $request, InvestmentAccount $investment): RedirectResponse
    {
        $investment->load('customer');
        abort_unless($investment->customer?->isVisibleTo(auth()->user()), 403, 'This member is assigned to another account officer.');
        abort_unless($investment->status === 'pending', 422);

        $validated = $request->validate([
            'rejected_reason' => ['required', 'string', 'max:1000'],
        ]);

        $investment->update([
            'status' => 'rejected',
            'balance' => 0,
            'rejected_at' => now(),
            'rejected_reason' => $validated['rejected_reason'],
        ]);

        ActivityLogger::log('investment_account_rejected', 'Investment '.$investment->account_number.' rejected.', $investment);

        return back()->with('status', 'Investment rejected.');
    }

    public function storeTransaction(Request $request, InvestmentAccount $investment, MemberNotificationAutomationService $notifications): RedirectResponse
    {
        $investment->load('customer');
        abort_unless($investment->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');

        if ($investment->status !== 'active') {
            return back()->with('warning', 'This investment account must be active before posting transactions.');
        }

        $validated = $request->validate([
            'type' => ['required', Rule::in(['deposit', 'return', 'withdrawal', 'maturity_payout'])],
            'amount' => ['required', 'numeric', 'min:0.01'],
            'method' => ['required', 'string', 'max:50'],
            'description' => ['nullable', 'string', 'max:1000'],
        ]);

        $amount = (float) $validated['amount'];
        $isDebit = in_array($validated['type'], ['withdrawal', 'maturity_payout'], true);

        if ($isDebit && $amount > (float) $investment->balance) {
            return back()->with('warning', 'Insufficient investment balance.');
        }

        $transaction = DB::transaction(function () use ($request, $investment, $validated, $amount, $isDebit): InvestmentTransaction {
            $before = (float) $investment->balance;
            $isDeposit = $validated['type'] === 'deposit';
            $after = $isDebit ? $before - $amount : $before + $amount;
            $updates = ['balance' => $after];

            if ($isDeposit) {
                return InvestmentTransaction::create([
                    'tenant_id' => $investment->tenant_id,
                    'investment_account_id' => $investment->id,
                    'customer_id' => $investment->customer_id,
                    'created_by' => $request->user()->id,
                    'type' => 'deposit',
                    'amount' => $amount,
                    'balance_before' => $before,
                    'balance_after' => $before,
                    'reference' => NumberGenerator::reference('INVDEP'),
                    'method' => $validated['method'],
                    'status' => 'pending',
                    'description' => $validated['description'] ?? null,
                    'transacted_at' => now(),
                ]);
            }

            if ($validated['type'] === 'return') {
                $updates['return_paid'] = (float) $investment->return_paid + $amount;
            }

            if ($validated['type'] === 'maturity_payout' && $after <= 0.0) {
                $updates['status'] = 'closed';
                $updates['closed_at'] = now();
                $updates['matured_at'] = now();
            }

            $transaction = InvestmentTransaction::create([
                'tenant_id' => $investment->tenant_id,
                'investment_account_id' => $investment->id,
                'customer_id' => $investment->customer_id,
                'created_by' => $request->user()->id,
                'type' => $validated['type'],
                'amount' => $amount,
                'balance_before' => $before,
                'balance_after' => $after,
                'reference' => NumberGenerator::reference(match ($validated['type']) {
                    'return' => 'INVRET',
                    'withdrawal' => 'INVWDL',
                    'maturity_payout' => 'INVPAY',
                    default => 'INVDEP',
                }),
                'method' => $validated['method'],
                'status' => 'posted',
                'approved_by' => $request->user()->id,
                'approved_at' => now(),
                'description' => $validated['description'] ?? null,
                'transacted_at' => now(),
            ]);

            $investment->update($updates);

            return $transaction;
        });

        if ($validated['type'] === 'deposit') {
            ActivityLogger::log('investment_deposit_pending', 'Investment deposit submitted for approval on '.$investment->account_number, $transaction);

            return back()->with('status', 'Investment deposit submitted for approval.');
        }

        ActivityLogger::log('investment_transaction_'.$validated['type'], ucfirst(str_replace('_', ' ', $validated['type'])).' posted on '.$investment->account_number, $transaction);
        if (in_array($validated['type'], ['withdrawal', 'maturity_payout'], true)) {
            $transaction->load('customer');
            $notifications->send($transaction->customer, 'investment_withdrawal_posted', [
                'type' => str_replace('_', ' ', $transaction->type),
                'amount' => $transaction->amount,
                'balance_after' => $transaction->balance_after,
                'reference' => $transaction->reference,
            ]);
        }

        return back()->with('status', 'Investment transaction recorded.');
    }

    public function approveTransaction(InvestmentTransaction $transaction, MemberNotificationAutomationService $notifications): RedirectResponse
    {
        $transaction->load(['account.customer']);
        abort_unless($transaction->customer?->isVisibleTo(auth()->user()), 403, 'This member is assigned to another account officer.');
        abort_unless($transaction->status === 'pending', 422);

        DB::transaction(function () use ($transaction): void {
            $lockedTransaction = InvestmentTransaction::whereKey($transaction->id)->lockForUpdate()->firstOrFail();
            abort_unless($lockedTransaction->status === 'pending', 422);

            $account = InvestmentAccount::with('product')->whereKey($lockedTransaction->investment_account_id)->lockForUpdate()->firstOrFail();
            $before = (float) $account->balance;
            $amount = (float) $lockedTransaction->amount;
            $isDebit = in_array($lockedTransaction->type, ['withdrawal', 'maturity_payout'], true);
            $after = $isDebit ? $before - $amount : $before + $amount;

            abort_if($isDebit && $amount > $before, 422, 'Insufficient investment balance.');

            $updates = ['balance' => $after];

            if ($lockedTransaction->type === 'deposit') {
                $updates['amount'] = (float) $account->amount + $amount;
                $updates['expected_return'] = (float) $account->expected_return + $this->expectedReturn(
                    $amount,
                    (float) $account->interest_rate,
                    (int) $account->term_value,
                    (string) $account->term_unit,
                    (string) ($account->product?->return_type ?? 'fixed'),
                );
            }

            $lockedTransaction->update([
                'status' => 'posted',
                'balance_before' => $before,
                'balance_after' => $after,
                'approved_by' => auth()->id(),
                'approved_at' => now(),
                'transacted_at' => $lockedTransaction->transacted_at ?: now(),
            ]);

            $account->update($updates);
        });

        ActivityLogger::log('investment_deposit_approved', 'Investment transaction '.$transaction->reference.' approved.', $transaction);
        $transaction->refresh()->load(['customer', 'creator', 'approver']);
        $notifications->send($transaction->customer, 'investment_deposit_approved', [
            'amount' => $transaction->amount,
            'balance_after' => $transaction->balance_after,
            'reference' => $transaction->reference,
        ]);

        return back()->with('status', 'Investment deposit approved and balance updated.');
    }

    public function rejectTransaction(Request $request, InvestmentTransaction $transaction): RedirectResponse
    {
        $transaction->load(['account.customer']);
        abort_unless($transaction->customer?->isVisibleTo(auth()->user()), 403, 'This member is assigned to another account officer.');
        abort_unless($transaction->status === 'pending', 422);

        $validated = $request->validate([
            'rejected_reason' => ['required', 'string', 'max:1000'],
        ]);

        $description = trim((string) $transaction->description);
        $transaction->update([
            'status' => 'rejected',
            'description' => trim($description."\nRejected: ".$validated['rejected_reason']),
        ]);

        ActivityLogger::log('investment_deposit_rejected', 'Investment transaction '.$transaction->reference.' rejected.', $transaction);

        return back()->with('status', 'Investment deposit rejected.');
    }

    public function cancelTransaction(Request $request, InvestmentTransaction $transaction, TransactionCancellationService $cancellations): RedirectResponse
    {
        $transaction->load(['account.customer']);
        abort_unless($transaction->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');

        $validated = $request->validate([
            'canceled_reason' => ['required', 'string', 'max:1000'],
        ]);

        try {
            $cancellations->cancelInvestment($transaction, $validated['canceled_reason'], $request->user()->id);
        } catch (InvalidArgumentException $exception) {
            return back()->with('warning', $exception->getMessage());
        }

        return back()->with('status', 'Investment transaction canceled and the account balance was reversed.');
    }

    private function validatedProduct(Request $request, ?InvestmentProduct $product = null): array
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:255', Rule::unique('investment_products')->where('tenant_id', $request->user()->tenant_id)->ignore($product)],
            'description' => ['nullable', 'string', 'max:1000'],
            'investment_number_prefix' => ['nullable', 'string', 'max:20', 'regex:/^[A-Za-z0-9]+$/'],
            'interest_rate' => ['required', 'numeric', 'min:0'],
            'return_type' => ['required', Rule::in(array_keys(InvestmentProduct::RETURN_TYPES))],
            'term_value' => ['required', 'integer', 'min:1', 'max:3650'],
            'term_unit' => ['required', Rule::in(array_keys(InvestmentProduct::TERM_UNITS))],
            'minimum_amount' => ['required', 'numeric', 'min:0'],
            'maximum_amount' => ['required', 'numeric', 'min:0'],
            'allow_member_request' => ['nullable', 'boolean'],
            'show_on_member_portal' => ['nullable', 'boolean'],
            'show_on_public_website' => ['nullable', 'boolean'],
            'is_active' => ['nullable', 'boolean'],
        ]);

        foreach (['allow_member_request', 'show_on_member_portal', 'show_on_public_website', 'is_active'] as $field) {
            $validated[$field] = $request->has($field)
                ? $request->boolean($field)
                : (bool) ($product?->{$field} ?? true);
        }

        $validated['investment_number_prefix'] = filled($validated['investment_number_prefix'] ?? null)
            ? strtoupper($validated['investment_number_prefix'])
            : 'INV';

        return $validated;
    }

    private function maturityDate(Carbon $date, int $termValue, string $termUnit): Carbon
    {
        match ($termUnit) {
            'days' => $date->addDays($termValue),
            'weeks' => $date->addWeeks($termValue),
            'years' => $date->addYears($termValue),
            default => $date->addMonthsNoOverflow($termValue),
        };

        return $date;
    }

    private function expectedReturn(float $amount, float $rate, int $termValue, string $termUnit, string $returnType): float
    {
        if ($rate <= 0) {
            return 0;
        }

        $years = match ($termUnit) {
            'days' => $termValue / 365,
            'weeks' => ($termValue * 7) / 365,
            'years' => $termValue,
            default => $termValue / 12,
        };

        if ($returnType === 'compound') {
            return round($amount * ((1 + ($rate / 100)) ** max($years, 0)) - $amount, 2);
        }

        return round($amount * ($rate / 100) * max($years, 0), 2);
    }

    private function progress(InvestmentAccount $account): array
    {
        $progress = $account->progressPercent();
        $expectedTotal = (float) $account->amount + (float) $account->expected_return;
        $remainingReturn = max(0, (float) $account->expected_return - (float) $account->return_paid);

        return compact('progress', 'expectedTotal', 'remainingReturn');
    }
}
