<?php

namespace App\Http\Controllers\Frontend;

use App\Data\TransactionData;
use App\Enums\AmountFlow;
use App\Enums\MethodType;
use App\Enums\TrxStatus;
use App\Enums\TrxType;
use App\Exceptions\NotifyErrorException;
use App\Http\Controllers\Controller;
use App\Models\DepositMethod;
use App\Models\Merchant;
use App\Services\Handlers\PaymentHandler;
use Exception;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Payment;
use Throwable;
use Transaction;
use Wallet;

class MerchantPaymentReceiveController extends Controller
{
    /**
     * Display the payment checkout page.
     *
     * @return View|JsonResponse
     */
    public function paymentCheckout(Request $request)
    {
        if (! $request->hasValidSignature()) {
            return response()->json(['error' => 'Invalid or expired link'], 403);
        }

        $token = $request->query('token');

        if (! $token) {
            return response()->json(['error' => 'Invalid transaction request'], 400);
        }

        try {
            $trxId = Crypt::decryptString($token);
        } catch (Exception $e) {
            return response()->json(['error' => 'Invalid or tampered token'], 400);
        }

        $transaction = Transaction::findTransaction($trxId);

        if (! $transaction || $transaction->status !== TrxStatus::PENDING) {
            abort(404, 'Not Valid Transaction');
        }

        $merchant = Merchant::findOrFail($transaction->trx_data['merchant_id'])
            ->only(['business_name', 'business_logo']);

        $data = array_merge($transaction->trx_data, $merchant, [
            'payment_amount' => "{$transaction->payable_amount} {$transaction->payable_currency}",
        ]);

        $paymentMethods = DepositMethod::where([
            ['type', MethodType::AUTOMATIC],
            ['status', true],
            ['currency', $transaction->payable_currency],
        ])->get();

        return view('general.merchant.payment_checkout', compact('data', 'paymentMethods', 'trxId'));
    }

    /**
     * Process the selected payment method.
     */
    public function processPayment(Request $request)
    {
        $validated = $request->validate([
            'trx_id'          => 'required',
            'selected_method' => 'required',
        ]);

        $transaction = Transaction::findTransaction($validated['trx_id']);

        if (! $transaction) {
            abort(404);
        }

        if ($validated['selected_method'] === MethodType::SYSTEM->value) {
            $data = array_merge($transaction->trx_data, [
                'payment_amount' => "{$transaction->payable_amount} {$transaction->payable_currency}",
                'currency'       => $transaction->payable_currency,
            ]);

            return view('general.merchant.payment_wallet', [
                'data'  => $data,
                'trxId' => $validated['trx_id'],
            ]);
        }

        $provider = title($validated['selected_method']);

        $transaction->update([
            'provider'    => $provider,
            'description' => __('Payment via :provider from :merchant', ['provider' => $provider, 'merchant' => $transaction->trx_data['merchant_name']]),
        ]);

        // Get the payment gateway URL from the service
        $redirectUrl = Payment::paymentWithPaymentMethod(
            $validated['selected_method'],
            $transaction
        );

        // Redirect the user to the payment gateway
        if (filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
            // Redirect the user to the payment gateway for URL
            return redirect()->away($redirectUrl);
        } else {
            // Return the HTML view directly if $redirectUrl is HTML content
            return response($redirectUrl, 200)->header('Content-Type', 'text/html');

        }

    }

    /**
     * Complete payment using wallet credentials provided by the user.
     *
     * @throws NotifyErrorException
     */
    public function completePayment(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'trx_id'    => 'required',
            'password'  => 'required',
            'wallet_id' => 'required',
        ]);

        $userWallet          = Wallet::getWalletByUniqueId($validated['wallet_id']);
        $merchantTransaction = Transaction::findTransaction($validated['trx_id']);
        $trxData             = $merchantTransaction->trx_data;

        $merchant = Merchant::findOrFail($trxData['merchant_id']);
        $user     = $userWallet->user;

        if (! Hash::check($validated['password'], $user->password)) {
            throw new NotifyErrorException(__('Incorrect password for this wallet user.'));
        }

        return $this->processMerchantTransaction($userWallet, $merchantTransaction, $user, $merchant);
    }

    /**
     * Complete payment using the currently authenticated user's wallet.
     *
     * @throws NotifyErrorException
     */
    public function payWithAccount(Request $request): RedirectResponse
    {

        if ($request->isMethod('get')) {
            $token = $request->get('token'); // Get the transaction ID from the query parameter $trxId

            $payload = Payment::verifyTokenAndGetData($token);

            if (! isset($payload['trx_id'])) {
                throw new NotifyErrorException('Invalid or expired token.');
            }
            $trxId = $payload['trx_id'];

        } else {
            $validated = $request->validate([
                'trx_id' => 'required',
            ]);
            $trxId = $validated['trx_id'];
        }

        $merchantTransaction = Transaction::findTransaction($trxId);

        if (! $merchantTransaction) {
            abort(404);
        }
        $trxData = $merchantTransaction->trx_data;

        $merchant   = Merchant::findOrFail($trxData['merchant_id']);
        $user       = auth()->user();
        $userWallet = Wallet::getWalletByUserId($user->id, $merchant->currency->code);

        return $this->processMerchantTransaction($userWallet, $merchantTransaction, $user, $merchant);
    }

    /**
     * Handle a failed transaction by sending an IPN notification and redirecting.
     */
    protected function handleFailure($transaction, string $errorMessage, ?string $description = null): RedirectResponse
    {
        Transaction::failTransaction($transaction->trx_id, $errorMessage, $description);

        return redirect()->away($transaction->trx_data['cancel_redirect']);
    }

    /**
     * Process the merchant transaction after validations.
     */
    private function processMerchantTransaction(mixed $userWallet, mixed $merchantTransaction, mixed $user, mixed $merchant): RedirectResponse
    {

        if ($user->id === $merchantTransaction->user_id) {
            return $this->handleFailure($merchantTransaction, __('You cannot pay to yourself.'), __('A payment attempt via User Wallet from :merchant customer has failed', ['merchant' => $merchant->business_name]));
        }
        $trxData        = $merchantTransaction->trx_data;
        $payableAmount  = $merchantTransaction->payable_amount;
        $merchantAmount = $merchantTransaction->amount;

        if (! Wallet::isWalletBalanceSufficient($userWallet->uuid, $payableAmount)) {
            return $this->handleFailure($merchantTransaction, __('Insufficient balance'));
        }

        $merchantWallet = Wallet::getWalletByUserId($merchantTransaction->user_id, $merchant->currency->code);

        try {
            $this->executeTransaction($userWallet, $merchantWallet, $payableAmount, $merchantAmount, $user, $merchant);

            Transaction::completeTransaction($merchantTransaction->trx_id, null, __('You have received a payment via User Wallet from :merchant customer', ['merchant' => $merchant->business_name]));

            return redirect()->away($trxData['success_redirect']);
        } catch (Exception $e) {
            Log::error('Transaction process failed: '.$e->getMessage());

            return $this->handleFailure(
                $trxData,
                __('Transaction failed'),
                __('A payment attempt via User Wallet from :merchant customer has failed', ['merchant' => $merchant->business_name])
            );
        }

    }

    /**
     * Execute the wallet transaction atomically.
     *
     * @throws Throwable
     */
    private function executeTransaction(
        mixed $userWallet,
        mixed $merchantWallet,
        float $payableAmount,
        float $merchantAmount,
        mixed $user,
        mixed $merchant
    ): void {
        DB::transaction(function () use ($userWallet, $payableAmount, $user, $merchant) {

            $paymentHandler = app(PaymentHandler::class);

            $transaction = Transaction::create(new TransactionData(
                user_id: $user->id,
                trx_type: TrxType::PAYMENT,
                amount: $payableAmount,
                amount_flow: AmountFlow::MINUS,
                currency: $merchant->currency->code,
                net_amount: $payableAmount,
                payable_amount: $payableAmount,
                payable_currency: $merchant->currency->code,
                wallet_reference: $userWallet->uuid,
                description: __('Payment to :merchant via your wallet', ['merchant' => $merchant->business_name]),
                status: TrxStatus::COMPLETED
            ));

            $paymentHandler->handleSuccess($transaction);
        });
    }
}
