<?php

namespace App\Http\Controllers\Finance;

use App\Actions\CreateActivity;
use App\Http\Controllers\Controller;
use App\Models\Currency;
use App\Models\CustomBilingPlans;
use App\Models\GatewayProducts;
use App\Models\Gateways;
use App\Models\PaymentPlans;
use App\Models\Setting;
use App\Models\User;
use App\Models\UserOrder;
// use App\Models\Subscriptions;
use App\Services\GatewaySelector;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Laravel\Cashier\Subscription as Subscriptions;

class PaymentProcessController extends Controller
{
    // payment area
    public function isActiveSubscription($planId) // Checks subscription table if given plan is active on user (already subscribed)
    {
        $activeSub = getCurrentActiveSubscription();
        if ($activeSub != null) {
            $activesubid = $activeSub->id;
        } else {
            $activeSub_yokassa = getCurrentActiveSubscriptionYokkasa();
            if ($activeSub_yokassa != null) {
                $activesubid = $activeSub_yokassa->id;
            } else {
                $activesubid = 0; //id can't be zero, so this will be easy to check instead of null
            }
        }

        return $activesubid == $planId;
    }

    public function startSubscriptionProcess($planId, $gatewayCode)
    { // when click on subscribe
        $plan = PaymentPlans::where('id', $planId)->first();
        if ($plan != null) {
            if (self::isActiveSubscription($planId) == true) {
                return back()->with([
                    'message' => __('You already have subscription. Please cancel it before creating a new subscription'),
                    'type'    => 'error',
                ]);
            }
            if ($gatewayCode == 'walletmaxpay') {
                return back()->with([
                    'message' => __('WalletMaxPay available only for Token Packs'), 'type' => 'error',
                ]);
            }

            try {
                return GatewaySelector::selectGateway($gatewayCode)::subscribe($plan);
            } catch (Exception $e) {
                return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
            }
        }
        abort(404);
    }

    public function startPrepaidPaymentProcess($planId, $gatewayCode)
    {
        $plan = PaymentPlans::where('id', $planId)->first();
        if ($plan != null) {
            try {
                return GatewaySelector::selectGateway($gatewayCode)::prepaid($plan);
            } catch (Exception $e) {
                return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
            }
        }
        abort(404);
    }

    public function startSubscriptionCheckoutProcess(Request $request, $gateway = null, $referral = null)
    {
        if ($gateway !== 'freeservice' && $request->isMethod('post')) {

            $gateways = Gateways::where('is_active', 1)->pluck('code')->toArray();
            $request->validate([
                'planID'   => 'required',
                'orderID'  => 'nullable',
                'couponID' => 'nullable',
                'gateway'  => ['required', 'in:' . implode(',', $gateways)],
            ]);
            if ($gateway == null) {
                $gateway = $request->gateway;
            }
        }

        try {
            return GatewaySelector::selectGateway($gateway)::subscribeCheckout($request, $referral);
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    public function startPrepaidCheckoutProcess(Request $request, $gateway = null, $referral = null)
    {
        if ($gateway !== 'freeservice' && $request->isMethod('post')) {
            $gateways = Gateways::where('is_active', 1)->pluck('code')->toArray();
            $request->validate([
                'planID'   => 'required',
                'orderID'  => 'nullable',
                'couponID' => 'nullable',
                'gateway'  => ['required', 'in:' . implode(',', $gateways)],
            ]);
            if ($gateway == null) {
                $gateway = $request->gateway;
            }
        }

        try {
            return GatewaySelector::selectGateway($gateway)::prepaidCheckout($request, $referral);
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    // additional required functions
    public function createPayPalOrder(Request $request)
    {
        try {
            return GatewaySelector::selectGateway('paypal')::createPayPalOrder($request);
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    public function iyzicoPrepaidCallback(Request $request)
    {
        try {
            return GatewaySelector::selectGateway('iyzico')::iyzicoPrepaidCallback($request);
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    public function iyzicoSubscribeCallback(Request $request)
    {
        try {
            return GatewaySelector::selectGateway('iyzico')::iyzicoSubscribeCallback($request);
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    public function iyzicoProductsList(Request $request)
    {
        try {
            return GatewaySelector::selectGateway('iyzico')::iyzicoProductsList($request);
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    public function succesful()
    {
        return view('panel.user.finance.succesful');
    }

    // webhook control area
    public function handleWebhook(Request $request, $gateway)
    {
        // [stripe,paypal,yokassa,iyzico,paystack,twocheckout]
        try {
            if ($request->isMethod('post')) { // accept the post method for all
                if ($gateway == 'simulate') {
                    return GatewaySelector::selectGateway($gateway)::simulateWebhookEvent($request);
                }

                return GatewaySelector::selectGateway($gateway)::handleWebhook($request);
            } elseif ($request->isMethod('get')) { // accept the get method only for [twocheckout, simulate]
                if ($gateway == 'simulate') {
                    return GatewaySelector::selectGateway('paypal')::simulateWebhookEvent($request);
                } elseif ($gateway == 'twocheckout') {
                    return GatewaySelector::selectGateway($gateway)::handleWebhook($request);
                } elseif ($gateway == 'razorpay') {
                    return GatewaySelector::selectGateway($gateway)::handleWebhook($request);
                } else {
                    abort(404);
                }
            }
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    // admin control area
    public static function banktransactions()
    {
        $bankOrders = UserOrder::where('payment_type', 'banktransfer')->orderByRaw("CASE WHEN status = 'Waiting' THEN 0 ELSE 1 END")->orderBy('created_at', 'desc')->get();

        return view('panel.admin.banktransfer.index', compact('bankOrders'));
    }

    public static function bankDelete($id = null)
    {
        $post = UserOrder::findOrFail($id);
        $post->delete();

        return back()->with(['message' => __('Deleted Successfully'), 'type' => 'success']);
    }

    public static function bankUpdateSave(Request $request)
    {
        if ($request->order_status != 0) {
            $order = UserOrder::findOrFail($request->order_id);
            self::changeOrderStatus($request->order_status, $order);
        }

        return back()->with(['message' => __('Updated Successfully'), 'type' => 'success']);
    }

    private static function changeOrderStatus($status, $order)
    {
        switch ($status) {
            case 'Waiting':
                // sent mail if required here later
                CreateActivity::for($order->user, __('Bank transaction status updated to:') . ' ' . __($status), $order->plan->name . ' ' . __('Plan'));

                break;
            case 'Approved':
                if ($order->type == 'subscription') {
                    $subs = Subscriptions::where('stripe_id', $order->order_id)->first();
                    if ($subs) {
                        $subs->stripe_status = 'bank_approved';

                        switch ($order->plan->frequency) {
                            case 'monthly':
                                $subs->ends_at = \Carbon\Carbon::now()->addMonths(1);

                                break;
                            case 'yearly':
                                $subs->ends_at = \Carbon\Carbon::now()->addYears(1);

                                break;
                            case 'lifetime_monthly':
                                $subs->ends_at = \Carbon\Carbon::now()->addMonths(1); //ends each month but auto renewing without payment reqs
                                $subs->auto_renewal = 1;

                                break;
                            case 'lifetime_yearly':
                                $subs->ends_at = \Carbon\Carbon::now()->addYears(1); //ends each year but auto renewing without payment reqs
                                $subs->auto_renewal = 1;

                                break;
                            default:
                                $subs->ends_at = \Carbon\Carbon::now()->addMonths(1);

                                break;
                        }
                        $subs->save();
                    }
                }
                $order->plan->total_words == -1 ? ($order->user->remaining_words = -1) : ($order->user->remaining_words += $order->plan->total_words);
                $order->plan->total_images == -1 ? ($order->user->remaining_images = -1) : ($order->user->remaining_images += $order->plan->total_images);
                $order->user->save();
                // sent mail if required here later
                CreateActivity::for($order->user, __('Purchased with approved bank transaction'), $order->plan->name . ' ' . __('Plan'));

                break;
            case 'Rejected':
                $subs = Subscriptions::where('stripe_id', $order->order_id)->first();
                if ($subs) {
                    $subs->stripe_status = 'bank_rejected';
                    $subs->save();
                }
                // sent mail if required here later
                CreateActivity::for($order->user, __('Bank transaction status updated to:') . ' ' . __($status), $order->plan->name . ' ' . __('Plan'));

                break;
            default:
                break;
        }

        $order->status = $status;
        $order->save();
    }

    public static function getSubscriptionStatus()
    {
        $gateway = null;
        $activeSub = getCurrentActiveSubscription();

        if ($activeSub != null) {
            $gateway = $activeSub->paid_with;
        } else {
            $activeSubY = getCurrentActiveSubscriptionYokkasa();
            if ($activeSubY != null) {
                $gateway = 'yokassa';
            }
        }

        try {
            return GatewaySelector::selectGateway($gateway)::getSubscriptionStatus();
        } catch (Exception $e) {
            return false;
        }
    }

    public static function getSubscriptionDaysLeft()
    {
        $gateway = null;
        $activeSub = getCurrentActiveSubscription();
        if ($activeSub != null) {
            $gateway = $activeSub->paid_with;
        } else {
            $activeSubY = getCurrentActiveSubscriptionYokkasa();
            if ($activeSubY != null) {
                $gateway = 'yokassa';
            }
        }

        try {
            return GatewaySelector::selectGateway($gateway)::getSubscriptionDaysLeft();
        } catch (Exception $e) {
            return null;
        }
    }

    public static function cancelActiveSubscription()
    {
        $gateway = null;
        $activeSub = getCurrentActiveSubscription();
        if ($activeSub != null) {
            $gateway = $activeSub->paid_with;
        } else {
            $activeSubY = getCurrentActiveSubscriptionYokkasa();
            if ($activeSubY != null) {
                $gateway = 'yokassa';
            }
        }

        try {
            return GatewaySelector::selectGateway($gateway, 'Could not cancel subscription. Please try again. If this error occures again, please update and migrate.')::subscribeCancel();
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    public static function checkIfTrial()
    {
        $gateway = null;
        $activeSub = getCurrentActiveSubscription();
        if ($activeSub != null) {
            $gateway = $activeSub->paid_with;
        } else {
            $activeSubY = getCurrentActiveSubscriptionYokkasa();
            if ($activeSubY != null) {
                $gateway = 'yokassa';
            }
        }

        try {
            if ($gateway == null) {
                return false;
            }

            return GatewaySelector::selectGateway($gateway)::checkIfTrial();
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    public static function getSubscriptionRenewDate()
    {
        $gateway = null;
        $activeSub = getCurrentActiveSubscription();
        if ($activeSub != null) {
            $gateway = $activeSub->paid_with;
        } else {
            $activeSubY = getCurrentActiveSubscriptionYokkasa();
            if ($activeSubY != null) {
                $gateway = 'yokassa';
            }
        }

        try {
            return GatewaySelector::selectGateway($gateway)::getSubscriptionRenewDate();
        } catch (Exception $e) {
            return null;
        }
    }

    public static function deletePaymentPlan($id)
    {
        $plan = PaymentPlans::where('id', $id)->first();
        if ($plan != null) {
            // Get related subscriptions
            $queryAnd = [['stripe_status', '=', 'active'], ['plan_id', '=', $plan->id]];
            $queryOr = [['stripe_status', '=', 'trialing'], ['plan_id', '=', $plan->id]];
            $queryOr2 = [['stripe_status', '=', 'bank_approved'], ['plan_id', '=', $plan->id]];
            $queryOr3 = [['stripe_status', '=', 'bank_renewed'], ['plan_id', '=', $plan->id]];
            $queryOr4 = [['stripe_status', '=', 'free_approved'], ['plan_id', '=', $plan->id]];
            $subscriptions = Subscriptions::where($queryAnd)
                ->orWhere($queryOr)
                ->orWhere($queryOr2)
                ->orWhere($queryOr3)
                ->orWhere($queryOr4)
                ->get();

            // Remove subcriptions one by one
            if ($subscriptions != null) {
                foreach ($subscriptions as $subscription) {
                    try {
                        $tmp = GatewaySelector::selectGateway($subscription->paid_with)::cancelSubscribedPlan($subscription, $plan->id);
                    } catch (Exception $e) {
                        return false;
                    }
                }
            }

            // Delete Plan
            $plan->delete();

            return back()->with([
                'message' => __('All subscriptions related to this plan has been cancelled. Plan is deleted.'),
                'type'    => 'success',
            ]);
        } else {
            return back()->with(['message' => 'Couldn\'t find plan.', 'type' => 'error']);
        }

    }

    public static function saveGatewayProducts($plan)
    {

        // $typ = $type == "prepaid" ? "o" : "s"; # o => one-time | s => subscription
        // switch ($frequency) {
        //     case 'monthly':
        //         $freq = "m";
        //         break;
        //     case 'yearly':
        //         $freq = "y";
        //         break;
        //     case 'lifetime_monthly':
        //         $freq = "lm";
        //         break;
        //     case 'lifetime_yearly':
        //         $freq = "ly";
        //         break;
        //     default:
        //         $freq = "m";
        //         break;
        // }

        $gateways = Gateways::all();
        if ($gateways != null) {
            foreach ($gateways as $gateway) {
                if ((int) $gateway->is_active == 1) {
                    try {
                        $tmp = GatewaySelector::selectGateway($gateway->code)::saveProduct($plan);
                    } catch (Exception $e) {
                    }
                }
            }
        } else {
            Log::error(self::$GATEWAY_CODE . '-> saveGatewayProducts(): Could not find any active gateways!');

            return back()->with(['message' => __('Please enable at least one gateway.'), 'type' => 'error']);
        }
    }

    public static function checkForOngoingPayments()
    {
        return null;
    }

    public static function checkUnmatchingSubscriptions()
    {
        $gateway = null;
        $activeSub = getCurrentActiveSubscription();
        if ($activeSub != null) {
            $gateway = $activeSub->paid_with;
        } else {
            $activeSubY = getCurrentActiveSubscriptionYokkasa();
            if ($activeSubY != null) {
                $gateway = 'yokassa';
                $activeSub = $activeSubY;
                $activeSub->paid_with = 'yokassa';
            }
        }
        if ($activeSub) {
            $priceArray = GatewayProducts::all()->pluck('price_id')->toArray();
            // if some plan cancelation happens when appling coupon then add the custom product price_id to custom bilings table
            $customPriceArray = CustomBilingPlans::all()->pluck('custom_plan_price_id')->toArray();
            if (in_array($activeSub->stripe_price, $priceArray) == true || in_array($activeSub->stripe_price, $customPriceArray) == true) {
                // Do nothing. This is what we want.
            } else {
                // Cancel subscription
                try {
                    if ($activeSub->paid_with != 'yokassa') {
                        $tmp = self::cancelActiveSubscription();
                    }
                } catch (Exception $ex) {
                    Log::error("PaymentProcessController::checkUnmatchingSubscriptions()\n" . $ex->getMessage());
                }
            }
            // Check if active subscription exists on gateway (by stripe_id / subscription id)
            // getSubscriptionStatus function is already called on subscription status file. BUT after functions which gives errors,
            // this needs priority, that's why we add here too. Also this function updates database as cancelled if can't find in gateway
            // there are webhooks for paypal and stripe and paystack
            if ($gateway != 'paypal' && $gateway != 'stripe' && $gateway != 'paystack') {
                $isValid = false;

                try {
                    $isValid = GatewaySelector::selectGateway($gateway)::getSubscriptionStatus();
                } catch (Exception $e) {
                }
            }
            // For some gateways we need to create orders first thats why we have so many Waiting order records. We must clean them.
            $orders = UserOrder::where([
                ['payment_type', '!=', 'banktransfer'], ['status', '=', 'Waiting'], ['user_id', '=', auth()->user()->id],
            ])->get();
            foreach ($orders as $order) {
                $order->delete();
            }
        }

        return null;
    }

    public static function cancelActiveSubscriptionByAdmin($userId)
    {
        $gateway = null;
        $activeSub = getCurrentActiveSubscription($userId);
        if ($activeSub != null) {
            $gateway = $activeSub->paid_with;
        } else {
            $activeSubY = getCurrentActiveSubscriptionYokkasa($userId);
            if ($activeSubY != null) {
                $gateway = 'yokassa';
            }
        }

        try {
            $user = User::find($userId);

            return GatewaySelector::selectGateway($gateway, 'Could not cancel subscription. Please try again. If this error occures again, please update and migrate.')::subscribeCancel($user);
        } catch (Exception $e) {
            return back()->with(['message' => $e->getMessage(), 'type' => 'error']);
        }
    }

    public static function assignPlanByAdmin(Request $request)
    {
        $request->validate([
            'planID' => 'required',
            'userID' => 'required',
        ]);
        $planID = $request->planID;
        $userID = $request->userID;

        $user = User::find($userID);
        $plan = PaymentPlans::where('id', $planID)->first();
        $total = $plan->price;

        $gatewayCode = 'freeservice';
        $tax = 0;
        $taxRate = 0;
        $status = 'free_approved';
        $gateway = Gateways::where('is_active', 1)->first();
        if ($gateway != null) {
            $taxValue = taxToVal($plan->price, $gateway->tax);
            $total += $taxValue;
            $gatewayCode = $gateway->code;
            $tax = $taxValue;
            $taxRate = $gateway->tax;
            $status = $gateway->code . '_approved';
        }

        $currency = Currency::where('id', $gateway->currency)->first()->code;
        $settings = Setting::first();

        try {
            DB::beginTransaction();
            // Create the subscription with the customer ID, price ID, and necessary options.
            $subscription = new Subscriptions;
            $subscription->user_id = $user->id;
            $subscription->name = $planID;
            $subscription->stripe_id = 'FLS-' . strtoupper(Str::random(13));
            $subscription->stripe_status = $status;
            $subscription->stripe_price = 'Not Needed';
            $subscription->quantity = 1;
            $subscription->trial_ends_at = null;
            $subscription->ends_at = $plan->frequency == 'lifetime_monthly' ? \Carbon\Carbon::now()->addMonths(1) : \Carbon\Carbon::now()->addYears(1);
            $subscription->auto_renewal = 1;
            $subscription->plan_id = $planID;
            $subscription->paid_with = $gatewayCode;
            $subscription->tax_rate = $taxRate;
            $subscription->tax_value = $tax;
            $subscription->total_amount = $total;
            $subscription->save();

            // save the order
            $order = new UserOrder;
            $order->order_id = $subscription->stripe_id;
            $order->plan_id = $planID;
            $order->user_id = $user->id;
            $order->payment_type = $gatewayCode;
            $order->price = $total;
            $order->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100;
            $order->status = 'Success';
            $order->country = $user->country ?? 'Unknown';
            $order->tax_rate = $taxRate;
            $order->tax_value = $tax;
            $order->save();
            // add plan credits
            $plan->total_words == -1 ? ($user->remaining_words = -1) : ($user->remaining_words += $plan->total_words);
            $plan->total_images == -1 ? ($user->remaining_images = -1) : ($user->remaining_images += $plan->total_images);
            $user->save();
            // inform the admin
            CreateActivity::for($user, __('Subscribed to'), $plan->name . ' ' . __('Plan'), null);

            DB::commit();

            return back()->with([
                'message' => __('The plan has been successfully assigned to the user.'), 'type' => 'success',
            ]);
        } catch (Exception $ex) {
            DB::rollBack();
            Log::error($gatewayCode . '-> assignPlan(): ' . $ex->getMessage());

            return back()->with(['message' => Str::before($ex->getMessage(), ':'), 'type' => 'error']);
        }
    }

    public static function assignTokenByAdmin(Request $request)
    {
        $request->validate([
            'token'  => 'required',
            'userID' => 'required',
        ]);
        $planID = $request->token;
        $userID = $request->userID;

        $user = User::find($userID);
        $plan = PaymentPlans::where('id', $planID)->first();

        $total = $plan->price;

        $gatewayCode = 'freeservice';
        $tax = 0;
        $taxRate = 0;
        $status = 'free_approved';
        $gateway = Gateways::where('is_active', 1)->first();
        if ($gateway != null) {
            $taxValue = taxToVal($plan->price, $gateway->tax);
            $total += $taxValue;
            $gatewayCode = $gateway->code;
            $tax = $taxValue;
            $taxRate = $gateway->tax;
            $status = $gateway->code . '_approved';
        }

        try {
            DB::beginTransaction();
            $order = new UserOrder;
            $order->order_id = 'ADM-' . strtoupper(Str::random(13));
            $order->plan_id = $plan->id;
            $order->user_id = $user->id;
            $order->type = 'prepaid';
            $order->payment_type = $gatewayCode;
            $order->price = $plan->price;
            $order->affiliate_earnings = 0;
            $order->status = 'Approved';
            $order->country = $user->country ?? 'Unknown';
            $order->tax_rate = 0;
            $order->tax_value = 0;
            $order->save();

            $order->plan->total_words == -1 ? ($order->user->remaining_words = -1) : ($order->user->remaining_words += $order->plan->total_words);
            $order->plan->total_images == -1 ? ($order->user->remaining_images = -1) : ($order->user->remaining_images += $order->plan->total_images);
            $order->user->save();
            // sent mail if required here later
            CreateActivity::for($order->user, __('Purchased'), $order->plan->name . ' ' . __('Plan') . ' ' . __('For free'), null);
            DB::commit();

            return back()->with([
                'message' => __('The plan has been successfully assigned to the user.'), 'type' => 'success',
            ]);
        } catch (Exception $th) {
            DB::rollBack();
            Log::error($gatewayCode . '-> subscribe(): ' . $th->getMessage());

            return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']);
        }
    }
}
