<?php

namespace App\Services;

use App\Models\User;
use App\Models\WalletTransfer;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class WalletTransferService
{
    public function __construct(
        protected WalletService $walletService,
        protected CommerceSettingService $commerceSettingService,
        protected UserActivityService $userActivityService
    ) {
    }

    public function settings(): array
    {
        return $this->commerceSettingService->walletTransferSettings();
    }

    /**
     * @return array{
     *   found:bool,
     *   reason?:string,
     *   message:string,
     *   beneficiary?:array{id:int,name:string,email:string,status:string,referral_code:string|null}
     * }
     */
    public function lookupBeneficiary(User $sender, string $recipientIdentifier): array
    {
        $recipient = $this->resolveRecipient(trim($recipientIdentifier));
        if (! $recipient) {
            return [
                'found' => false,
                'reason' => 'not_found',
                'message' => 'Beneficiary not found. Enter a valid registered email, referral code, or user ID.',
            ];
        }

        if ((int) $recipient->id === (int) $sender->id) {
            return [
                'found' => false,
                'reason' => 'self',
                'message' => 'You cannot transfer to your own wallet.',
            ];
        }

        if (method_exists($recipient, 'isActiveCustomer') && ! $recipient->isActiveCustomer()) {
            return [
                'found' => false,
                'reason' => 'suspended',
                'message' => 'Beneficiary account is suspended and cannot receive wallet transfer.',
            ];
        }

        return [
            'found' => true,
            'message' => 'Beneficiary verified.',
            'beneficiary' => [
                'id' => (int) $recipient->id,
                'name' => (string) $recipient->name,
                'email' => (string) $recipient->email,
                'status' => (string) ($recipient->status ?? 'active'),
                'referral_code' => $recipient->referral_code,
            ],
        ];
    }

    /**
     * @return array{
     *   sent_count:int,
     *   sent_amount:float,
     *   daily_count_limit:int,
     *   daily_total_limit:float,
     *   remaining_count:int,
     *   remaining_amount:float
     * }
     */
    public function usage(User $sender): array
    {
        $settings = $this->settings();
        [$sentCount, $sentAmount] = $this->todayTransferUsage($sender);

        $dailyCountLimit = max(1, (int) ($settings['daily_count_limit'] ?? 20));
        $dailyTotalLimit = round(max(1, (float) ($settings['daily_total_limit'] ?? 3000000)), 2);

        return [
            'sent_count' => $sentCount,
            'sent_amount' => round($sentAmount, 2),
            'daily_count_limit' => $dailyCountLimit,
            'daily_total_limit' => $dailyTotalLimit,
            'remaining_count' => max(0, $dailyCountLimit - $sentCount),
            'remaining_amount' => round(max(0, $dailyTotalLimit - $sentAmount), 2),
        ];
    }

    public function transfer(
        User $sender,
        string $recipientIdentifier,
        float $amount,
        ?string $narration = null,
        ?string $internalReference = null
    ): WalletTransfer {
        $settings = $this->settings();

        if (! ($settings['enabled'] ?? false)) {
            throw ValidationException::withMessages([
                'recipient' => 'Wallet transfer is currently disabled.',
            ]);
        }

        $normalizedAmount = round($amount, 2);
        $min = round((float) ($settings['min_amount'] ?? 100), 2);
        $max = round((float) ($settings['max_amount'] ?? 1000000), 2);

        if ($normalizedAmount < $min) {
            throw ValidationException::withMessages([
                'amount' => 'Minimum transfer amount is NGN '.number_format($min, 2).'.',
            ]);
        }

        if ($normalizedAmount > $max) {
            throw ValidationException::withMessages([
                'amount' => 'Maximum transfer amount is NGN '.number_format($max, 2).'.',
            ]);
        }

        $beneficiaryLookup = $this->lookupBeneficiary($sender, $recipientIdentifier);
        if (! ($beneficiaryLookup['found'] ?? false)) {
            throw ValidationException::withMessages([
                'recipient' => (string) ($beneficiaryLookup['message'] ?? 'Beneficiary not found.'),
            ]);
        }
        $recipientId = (int) data_get($beneficiaryLookup, 'beneficiary.id');
        $recipient = User::query()->find($recipientId);
        if (! $recipient) {
            throw (new ModelNotFoundException())->setModel(User::class, [$recipientId]);
        }

        $reference = (string) ($internalReference ?: $this->generateReference());
        $existing = WalletTransfer::query()->where('internal_reference', $reference)->first();
        if ($existing) {
            return $existing;
        }

        return DB::transaction(function () use ($sender, $recipient, $normalizedAmount, $narration, $reference) {
            $existing = WalletTransfer::query()->where('internal_reference', $reference)->lockForUpdate()->first();
            if ($existing) {
                return $existing;
            }

            $wallet = $this->walletService->getWallet($sender);
            DB::table('wallets')
                ->where('id', $wallet->id)
                ->lockForUpdate()
                ->first();

            $this->assertDailyTransferLimits($sender, $normalizedAmount);

            $transfer = WalletTransfer::create([
                'sender_user_id' => $sender->id,
                'recipient_user_id' => $recipient->id,
                'internal_reference' => $reference,
                'amount' => $normalizedAmount,
                'status' => 'pending',
                'narration' => $narration ?: 'Wallet to wallet transfer',
                'meta_json' => [
                    'recipient_email' => $recipient->email,
                    'recipient_name' => $recipient->name,
                    'sender_email' => $sender->email,
                    'sender_name' => $sender->name,
                ],
            ]);

            $senderLedgerReference = "wallet:transfer:debit:{$reference}";
            $recipientLedgerReference = "wallet:transfer:credit:{$reference}";

            try {
                $senderLedger = $this->walletService->debit(
                    user: $sender,
                    amount: $normalizedAmount,
                    reference: $senderLedgerReference,
                    narration: 'Wallet transfer to '.$recipient->name,
                    meta: [
                        'kind' => 'wallet_transfer',
                        'wallet_transfer_id' => $transfer->id,
                        'internal_reference' => $reference,
                        'recipient_user_id' => $recipient->id,
                    ],
                    source: 'wallet_transfer'
                );

                $recipientLedger = $this->walletService->credit(
                    user: $recipient,
                    amount: $normalizedAmount,
                    reference: $recipientLedgerReference,
                    narration: 'Wallet transfer from '.$sender->name,
                    meta: [
                        'kind' => 'wallet_transfer',
                        'wallet_transfer_id' => $transfer->id,
                        'internal_reference' => $reference,
                        'sender_user_id' => $sender->id,
                    ],
                    source: 'wallet_transfer'
                );
            } catch (\Throwable $e) {
                $transfer->update([
                    'status' => 'failed',
                    'last_error' => $e->getMessage(),
                ]);

                throw $e;
            }

            $transfer->update([
                'status' => 'success',
                'sender_ledger_reference' => $senderLedger->reference,
                'recipient_ledger_reference' => $recipientLedger->reference,
                'last_error' => null,
            ]);

            $this->userActivityService->touch($sender);
            $this->userActivityService->touch($recipient);

            return $transfer->fresh(['sender:id,name,email', 'recipient:id,name,email']);
        }, 3);
    }

    protected function assertDailyTransferLimits(User $sender, float $newAmount): void
    {
        $settings = $this->settings();
        [$sentCount, $sentAmount] = $this->todayTransferUsage($sender);

        $dailyCountLimit = max(1, (int) ($settings['daily_count_limit'] ?? 20));
        $dailyTotalLimit = round(max(1, (float) ($settings['daily_total_limit'] ?? 3000000)), 2);

        if ($sentCount >= $dailyCountLimit) {
            throw ValidationException::withMessages([
                'amount' => "Daily transfer count limit reached ({$dailyCountLimit}). Try again tomorrow.",
            ]);
        }

        $projectedTotal = round($sentAmount + $newAmount, 2);
        if ($projectedTotal > $dailyTotalLimit) {
            throw ValidationException::withMessages([
                'amount' => 'Daily transfer total limit exceeded. Remaining today: NGN '.number_format(
                    max(0, $dailyTotalLimit - $sentAmount),
                    2
                ).'.',
            ]);
        }
    }

    /**
     * @return array{0:int,1:float}
     */
    protected function todayTransferUsage(User $sender): array
    {
        $start = now()->startOfDay();
        $end = now()->endOfDay();

        $query = WalletTransfer::query()
            ->where('sender_user_id', $sender->id)
            ->whereBetween('created_at', [$start, $end])
            ->whereIn('status', ['pending', 'success']);

        $count = (int) (clone $query)->count();
        $amount = (float) ((clone $query)->sum('amount') ?? 0);

        return [$count, round($amount, 2)];
    }

    protected function resolveRecipient(string $identifier): ?User
    {
        $value = trim($identifier);
        if ($value === '') {
            return null;
        }

        if (ctype_digit($value)) {
            return User::query()->find((int) $value);
        }

        $email = Str::lower($value);
        $code = Str::upper($value);

        return User::query()
            ->whereRaw('LOWER(email) = ?', [$email])
            ->orWhere('referral_code', $code)
            ->first();
    }

    protected function generateReference(): string
    {
        return 'WTR-'.now()->format('YmdHis').'-'.Str::upper(Str::random(8));
    }
}
