<?php

namespace App\Modules\Company\Subscription\Payments;

use App\Jobs\Company\ProcessSendEmailPurchaseSubscription;
use App\Models\Company;
use App\Models\CompanySubscriptionPayments;
use App\Models\CompanySubscriptions;
use App\Modules\Company\Subscription\Discount\Coupon;
use App\Traits\CreateUserActionTraits;
use App\Traits\UserTraits;
use App\Traits\ValidatorTraits;
use App\Traits\RetrieveCustomerDataTraits;

use Exception;

use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;

use Carbon\Carbon;
use Stripe;

class MonthlyPayment
{
    use ValidatorTraits; 
    use UserTraits; 
    use CreateUserActionTraits; 
    use RetrieveCustomerDataTraits;

    public $currentUser;
    public $companySubscription;
    public $companyDetails;
    public $coupon;

    public function __construct()
    {
        Stripe\Stripe::setApiKey(config('stripe.secret_key'));

        $this->currentUser         = $this->getCurrentUser();
        $this->companySubscription = $this->fetchCompanySubscription($this->currentUser->company_id);
        $this->companyDetails      = $this->fetchCompanyDetails($this->currentUser->company_id);
        $this->coupon              = new Coupon;
    }

    public function payMonthlyBilling($payload)
    {
        if (!$payload['token']) 
        {
            return response()->json(['error' => 'Stripe payment token does not exist.']);
        }

        if (!$this->companySubscription) 
        {
            return response()->json(['error' => "Company subscription doesn't exist."]);
        }

        if (!$payload['amount_to_pay'] && $this->companySubscription->amount_due > 0) 
        {
            return response()->json(['error' => 'Amount to pay is required.']);
        }

        //! Coupon Discount Validation
        $couponData = false;

        if (isset($payload['couponCode'])) 
        {
            $couponData = $this->coupon->fetchCouponDetails($payload['couponCode']);
        
            if($couponData->for_all == false)
            {
                if($couponData->company_subscription_rates_id != $this->companySubscription->subscription_rate_id)
                {
                    return response()->json(['error' => 'Coupon code is not applicable on the choosen subscription rate.']); 
                }
            }

            if ($couponData->is_disabled) 
            {
                return response()->json(['error' => 'Coupon code is currently disabled.']);
            }

            $companyUsage = $this->coupon->checkCompanyUsage($this->currentUser->company_id);

            //! How many times the company can use the coupon
            if($companyUsage && $companyUsage->coupon_subscription_rates_id == $couponData->coupon_subscription_rates_id)
            {
                if($companyUsage->company_usage >= $companyUsage->range)
                {
                    return response()->json(['error' => 'You have already exceeded the maximum monthly range of the coupon.']);
                }
            }
        }

        $currentMonthPaid = $this->companySubscription->current_month_paid;

        $dateOfTransaction  = Carbon::now()->format('Y-m-d h:i:s');
        $startOfGracePeriod = $this->addDayToDate($currentMonthPaid, 6);
        $endOfGracePeriod   = $this->addDayToDate($currentMonthPaid, 30);

        $isTenDaysBelowBeforeExpiration = $this->isTenDaysBelowBeforeExpiration();
        $isCurrentPaidMonthHasPassed    = $this->isCurrentPaidMonthHasPassed();

        $currentAmountDue = $this->companySubscription->amount_due;

        $amountToPay = $payload['amount_to_pay'];
        $amountDue   = (float) $this->companySubscription->amount_due;

        $subscriptionRate = $this->companySubscription->rate;
        $newPenaltyCharge = 0;

        $couponDiscount = 0;

        if($couponData)
        {
            $couponDiscount = $this->getCouponCodeDiscount($couponData, 'monthly', $subscriptionRate);
        }

        $companyDiscount = 0;   

        if($this->companyDetails->discount)
        {
            $companyDiscount = $this->getDiscountedValue($this->companyDetails->discount, $subscriptionRate);
        }

        $note              = '';
        $newCouponDiscount = 0;

        /*
        Payment intended for
            #1 - on grace period (30 days after expiration), with penalty charge
            #2 - next billing,
            #3 - current billing with balance
        */
        
        //! For Grace Period billing
        if (Carbon::parse($dateOfTransaction)->format('Y-m-d') >= Carbon::parse($startOfGracePeriod)->format('Y-m-d')) 
        {
            $subscriptionRate    = $this->companySubscription->rate;
            $penaltyCharge       = $this->getPenaltyCharge();
            $newCurrentPaidMonth = $this->addDayToDate($currentMonthPaid, 30);
            $newPreviousBill     = 0;

            $totalAmountDue = ($amountDue + $subscriptionRate + $penaltyCharge) - ($companyDiscount + $couponDiscount);

            if($couponDiscount)
            {
                $newCouponDiscount = $couponDiscount;   
            }

            // Partial Payment
            if ($this->isPartialPayment($amountToPay, $totalAmountDue)) 
            {
                //* can be removed for tracing purpose only
                $note               = 'Partial Payment in grace period';
                $newCompanyDiscount = $companyDiscount;

                //? new discount will be generated based on the partial payment not on the total amount
                //? $newCompanyDiscount = $this->getDiscountedValue($this->companyDetails->discount, $amountToPay); 
                //? new generated total amount due based on new discount
                $newAmountDue = ($amountDue + $subscriptionRate + $penaltyCharge) - ($newCompanyDiscount + $couponDiscount);

                $newTotalDiscount  = $newCompanyDiscount + $couponDiscount;
                $newTotalAmountDue = $newAmountDue - $amountToPay;
                $newAmountPaid     = $amountToPay;
                $newTotalPaid      = $amountToPay;
                $newPenaltyCharge  = $penaltyCharge;
                $newPreviousBill   = $amountDue;
            } 
            else if ($amountToPay == $totalAmountDue) 
            {
                //! can be removed for tracing purpose only
                $note = 'Full Payment in grace period'; 

                //! new discount will be generated based on the partial payment not on the total amount
                $newCompanyDiscount = $companyDiscount;
                
                //!new generated total amount due based on new discount
                $newAmountDue = ($amountDue + $subscriptionRate + $penaltyCharge) - ($newCompanyDiscount + $couponDiscount);

                $newTotalDiscount  = $newCompanyDiscount + $couponDiscount;
                $newTotalAmountDue = $newAmountDue - $amountToPay;
                $newAmountPaid     = $amountToPay;
                $newPreviousBill   = $amountDue;
                $newTotalPaid      = $amountToPay;
                $newPenaltyCharge  = $penaltyCharge;
            }
            else if($amountToPay == 0 || $amountToPay < 0)
            {
                $note = "grace period, with discount bigger than the amount due";
                
                $currentAmountDue   = $this->companySubscription->amount_due;
                $newCompanyDiscount = $companyDiscount;
                $newTotalDiscount   = $newCompanyDiscount + $couponDiscount;
                $newPreviousBill    = $amountDue;
                $newTotalAmountDue  = ($currentAmountDue + $subscriptionRate + $penaltyCharge) - ($couponDiscount + $companyDiscount);
                
                $newAmountPaid    = 0;
                $newTotalPaid     = 0;
                $newPenaltyCharge = $penaltyCharge;
                $newAmountDue     = 0;

                if($newTotalAmountDue > 0 && $payload['couponCode'])
                {
                    return response()->json(['error' => "There is something wrong in your coupon. Please contact the system administrator for verification."]);
                }
            }
        } 
        else if ($isTenDaysBelowBeforeExpiration || $isCurrentPaidMonthHasPassed) 
        {
            //* For Next Month billing, NExt billing is always the first payment 
            $totalAmountDue      = ($amountDue + $subscriptionRate) - ($companyDiscount + $couponDiscount);
            $newPenaltyCharge    = 0;
            $newAmountPaid       = $amountToPay;
            $newCurrentPaidMonth = $this->addDayToDate($currentMonthPaid, 30);

            if($couponDiscount)
            {
                $newCouponDiscount = $couponDiscount;   
            }

            if ($this->isPartialPayment($amountToPay, $totalAmountDue)) 
            {
                $newCompanyDiscount = $companyDiscount;
                //* new generated total amount due based on new discount
                $newAmountDue       = ($amountDue + (float) $subscriptionRate) - ($newCompanyDiscount + $couponDiscount);  
                
                //! Check if its the first payment
                //* can be removed for tracing purpose only
                $note              = 'for next month payment, partial payment';  
                $newTotalDiscount  = $newCompanyDiscount + $couponDiscount;
                $newTotalAmountDue = $newAmountDue - $amountToPay;
                $newPreviousBill   = $amountDue;
                $newTotalPaid      = $amountToPay;
            } 
            else if ($amountToPay == $totalAmountDue) 
            {
                //* can be removed for tracing purpose only
                $note = 'for next month payment, full payment'; 

                $newCompanyDiscount = $companyDiscount;
                $newAmountDue = 0;
                $newTotalDiscount = $newCompanyDiscount + $couponDiscount;
                $newTotalAmountDue = 0;
                $newPreviousBill = $amountDue;
                $newTotalPaid = $amountToPay;
            }
            else if($amountToPay == 0 || $amountToPay < 0)
            {
                //* can be removed for tracing purpose only
                $note = 'for next month payment, discount bigger than amount due'; 
                
                $currentAmountDue   = $this->companySubscription->amount_due;
                $newCompanyDiscount = $companyDiscount;
                $newTotalDiscount   = $newCompanyDiscount + $couponDiscount;
                $newPreviousBill    = $amountDue;
                $newTotalPaid       = 0;
                $newAmountDue       = 0;
                $newTotalAmountDue  = ($currentAmountDue + $subscriptionRate) - ($couponDiscount + $companyDiscount);

                if($newTotalAmountDue > 0 && $payload['couponCode'])
                {
                    return response()->json(['error' => "There is something wrong in your coupon. Please contact the system administrator for verification."]);
                }
            }
        } 
        else 
        {
            //* Current billing
            $newCurrentPaidMonth     = $this->companySubscription->current_month_paid;

            //* Company discount from the previous payment if there is
            $consumedCompanyDiscount = (float) $this->companySubscription->discount;        
            $consumedCouponDiscount  = (float) $this->companySubscription->coupon_discount;
            $newCompanyDiscount      = 0;
            $companyDiscount         = 0;
            
            if($consumedCompanyDiscount)
            {
                $newCompanyDiscount = $consumedCompanyDiscount;
            }

            if($consumedCouponDiscount)
            {
                $newCouponDiscount = $consumedCouponDiscount;

                if($couponDiscount)
                {
                    $newCouponDiscount = $consumedCouponDiscount +  $couponDiscount;
                }
            }
            else if($couponDiscount)
            {
                $newCouponDiscount =  $couponDiscount;
            }

            $totalAmountDue   = ($amountDue) - ($couponDiscount);
            $newTotalDiscount = $newCompanyDiscount + $newCouponDiscount;

            if ($this->isPartialPayment($amountToPay, $totalAmountDue)) 
            {
                $note              = 'for the current billing, partial, nth payment';
                $currentAmountPaid = $this->companySubscription->total_paid;
                $newAmountPaid     = $amountToPay;

                //* new discount will be generated based on the partial payment not on the total amount
                //* new generated total amount due based on new discount
                $newAmountDue = $amountDue - ($couponDiscount);

                $newTotalDiscount  = $newCouponDiscount + $newCompanyDiscount;
                $newTotalAmountDue = $newAmountDue - $amountToPay;
                $newTotalPaid      = $currentAmountPaid + $amountToPay;
                $newPreviousBill   = 0;

                if ($this->companySubscription->penalty_charge > 0) 
                {
                    $newPenaltyCharge = $this->companySubscription->penalty_charge;
                }

                if ($this->companySubscription->previous_bill > 0) 
                {
                    $newPreviousBill = $this->companySubscription->previous_bill;
                }
            } 
            else if ($amountToPay == $totalAmountDue) 
            {
                //* can be removed for tracing purpose only
                $note              = 'for the current billing, full payment';
                $currentAmountPaid = $this->companySubscription->total_paid;

                //* new generated total amount due based on new discount
                $newAmountDue  = $amountDue - ($couponDiscount);
                $newAmountPaid = $newAmountDue;

                $newTotalDiscount  = $newCouponDiscount + $newCompanyDiscount;
                $newTotalAmountDue = $newAmountDue - $newAmountPaid;
                $newTotalPaid      = $currentAmountPaid + $newAmountPaid;
                $newPreviousBill   = 0;

                if ($this->companySubscription->penalty_charge > 0) 
                {
                    $newPenaltyCharge = $this->companySubscription->penalty_charge;
                }

                if ($this->companySubscription->previous_bill != 0) 
                {
                    $newPreviousBill = $this->companySubscription->previous_bill;
                }
            }
            else if($amountToPay == 0 || $amountToPay < 0)
            {
                $note            = 'for the current billing payment, discount is bigger than amount due ';
                $newTotalPaid    = $this->companySubscription->total_paid;
                $newPreviousBill = 0;
                $newAmountDue    = 0;
                $newAmountPaid   = 0;
                
                $currentAmountDue  = $this->companySubscription->amount_due;
                $currentAmountPaid = $this->companySubscription->total_paid;
                $newTotalDiscount  = $newCouponDiscount + $newCompanyDiscount;

                $newTotalAmountDue = ($currentAmountDue) - ($couponDiscount);

                if($newTotalAmountDue > 0 && $payload['couponCode'])
                {
                    return response()->json(['error' => "There is something wrong in your coupon. Please contact the system administrator for verification."]);
                }

                if ($this->companySubscription->previous_bill > 0) 
                {
                    $newPreviousBill = $this->companySubscription->previous_bill;
                }
            }
        }

        //! Check for payment situation: grace period, active (next month) or for current balance
        $paymentPayload = (object) 
        [
            'note'                => $note,
            'stripeToken'         => $payload['token'],
            'newAmountPaid'       => $newAmountPaid,
            'newTotalDiscount'    => $newTotalDiscount,
            'newCurrentPaidMonth' => $newCurrentPaidMonth,
            'newPreviousBill'     => $newPreviousBill,
            'newPenaltyCharge'    => $newPenaltyCharge,
            'currentAmountDue'    => $newAmountDue,
            
            'currentUserEmailAddress'     => $this->currentUser->email_address,
            'currentUserID'               => $this->currentUser->id,
            'currentSubscriptionDuration' => $this->companySubscription->duration,
            'currentSubscriptionRate'     => $this->companySubscription->rate,
            'companyID'                   => $this->currentUser->company_id,
            'stripeCustomerID'            => $this->companySubscription->customer_id,
            'companySubscriptionID'       => $this->companySubscription->id,
            'subcribedSubscriptionID'     => $this->companySubscription->subscription_rate_id,

            'newTotalAmountDue' => $newTotalAmountDue,
            'newTotalPaid'      => $newTotalPaid,

            'companyDiscount'    => $companyDiscount,
            'newCompanyDiscount' => $newCompanyDiscount,

            'couponData'        => $couponData,
            'couponCode'        => $payload['couponCode'] ?? null,
            'couponDiscount'    => $couponDiscount,
            'newCouponDiscount' => $newCouponDiscount,
        ];

        $user_data = 
        [
            'title'       => 'ConX Purchase Subscription Confirmation',
            'email'       => $this->currentUser->email_address,
            'currentUser' => $this->currentUser->first_name . ' ' . $this->currentUser->last_name,
            'amountPaid'  => round($newTotalPaid, 2),
            'duration'    => date("F j, Y", strtotime($this->companySubscription->duration))
        ];

        ProcessSendEmailPurchaseSubscription::dispatch($this->currentUser->email_address, $user_data)
            ->onConnection('jobs_emails')
            ->onQueue('sendemail');

        return $this->stripePayment($paymentPayload);
    }

    public function stripePayment($paymentPayload)
    {
        $invoiceNumber = 'IVN-' . Carbon::now()->format('Y-m-d-h-i-s') . '-' . Str::random(10);

        try 
        {
            DB::beginTransaction();

            $checkCustomer = $this->retrieveStripeCustomerData($paymentPayload->stripeCustomerID);

            if($checkCustomer)
            {
                $customer = \Stripe\Customer::update(
                    $paymentPayload->stripeCustomerID,
                    [
                        'email'    => $paymentPayload->currentUserEmailAddress,
                        'source'   => $paymentPayload->stripeToken,
                        "metadata" => 
                        [
                            "company_id" => $paymentPayload->companyID,
                        ],
                    ]
                );

                $stripeCustomerID = $paymentPayload->stripeCustomerID;

            }
            else
            {
                $customer = \Stripe\Customer::create(
                    [
                        'email'    => $paymentPayload->currentUserEmailAddress,
                        'source'   => $paymentPayload->stripeToken,
                        "metadata" => ["company_id" => $paymentPayload->companyID]
                    ]
                );

                $stripeCustomerID = $customer->id;
            }

            if($paymentPayload->newAmountPaid == 0 || $paymentPayload->newAmountPaid < 0)
            {
                //! Big discount, negative amount due
                $stripeCharge = (object)[
                    'id' => 'no-charge-transaction',
                    'status' => 'succeeded',
                    'receipt_url' => '',
                ];
            }
            else
            {
                $stripeCharge = \Stripe\Charge::create(
                    [
                        'customer' => $stripeCustomerID,
                        'amount' => $paymentPayload->newAmountPaid * 100,
                        'currency' => 'usd',
                        'receipt_email' => $paymentPayload->currentUserEmailAddress,
                    ]
                );
            }

            $newCompanySubscriptionDetails = 
            [
                'invoice_no'              => $invoiceNumber,
                'discount'                => $paymentPayload->newCompanyDiscount,
                'coupon_discount'         => $paymentPayload->newCouponDiscount,
                'total_discounted_amount' => $paymentPayload->newTotalDiscount,
                'amount_due'              => $paymentPayload->newTotalAmountDue,
                'amount_paid'             => $paymentPayload->newAmountPaid,
                'previous_bill'           => $paymentPayload->newPreviousBill,
                'penalty_charge'          => $paymentPayload->newPenaltyCharge,
                'total_paid'              => $paymentPayload->newTotalPaid,
                'current_month_paid'      => $paymentPayload->newCurrentPaidMonth,
            ];

            $companySubscriptionTransaction = $this->updateCompanySubscription($paymentPayload->companyID, $newCompanySubscriptionDetails);

            $paymentDetails = 
            [
                'company_subscription_id' => $paymentPayload->companySubscriptionID,
                'company_id'              => $paymentPayload->companyID,
                'customer_id'             => $stripeCustomerID,
                'subscription_rate_id'    => $paymentPayload->subcribedSubscriptionID,
                'charge_id'               => $stripeCharge->id,
                'user_id'                 => $paymentPayload->currentUserID,
                'invoice_no'              => $invoiceNumber,
                'duration'                => $paymentPayload->currentSubscriptionDuration,
                'rate'                    => $paymentPayload->currentSubscriptionRate,
                'balance'                 => $paymentPayload->newTotalAmountDue,
                'current_bill'            => $paymentPayload->currentAmountDue,
                'discount'                => $paymentPayload->newCompanyDiscount,
                'coupon_discount'         => $paymentPayload->newCouponDiscount,
                'total_discounted_amount' => $paymentPayload->newTotalDiscount,
                'previous_bill'           => $paymentPayload->newPreviousBill,
                'penalty_charge'          => $paymentPayload->newPenaltyCharge,
                'amount_paid'             => $paymentPayload->newAmountPaid,
                'month_paid'              => $paymentPayload->newCurrentPaidMonth,
                'type'                    => 'monthly',
                'total_paid'              => $paymentPayload->newTotalPaid,
                'is_blocked'              => 'false',
                'is_expired'              => 'false',
                'is_monthly_expired'      => 'false',
            ];

            $paymentTransaction = $this->createSubscriptionPayment($paymentDetails);

            $saveUserAction = $this->createUserAction("Paid the monthly payment subscription plan");

            if ($companySubscriptionTransaction && $paymentTransaction && $stripeCharge->status == "succeeded" && $saveUserAction) 
            {
                DB::commit();

                //! Add usage on coupon code
                if($paymentPayload->couponDiscount && $paymentPayload->couponData)
                {
                    $companyUsageData = 
                    [
                        'couponID' => $paymentPayload->couponData->id,
                        'companyID' => $paymentPayload->companyID,
                    ];

                    $this->coupon->addUsageOnCouponDiscount($companyUsageData);
                }

                return [
                    'result' => $stripeCharge->status,
                    'invoice_no' => $invoiceNumber,
                    'receipt_url' => $stripeCharge->receipt_url,
                    'note' => 'Successfully Paid!',
                    'exceed' => false,
                    'typeProcess' => $paymentPayload->note,
                ];
            } 
            else 
            {
                DB::rollBack();
            }
        } 
        catch (Exception $e) 
        {
            http_response_code(500);
            echo json_encode(['error' => $e->getMessage()]);
        }
    }

    public function fetchCompanySubscription($id)
    {
        return CompanySubscriptions::where('company_id', $id)->first();
    }

    public function fetchCompanyDetails($companyID)
    {
        return Company::select('discount')->where('id', $companyID)->first();
    }

    public function addDayToDate($dateToBeAdded, $days)
    {
        return Carbon::create($dateToBeAdded)->addDays($days)->format('Y-m-d h:i:s');
    }

    public function isTenDaysBelowBeforeExpiration()
    {
        $currentMonthPaid  = $this->companySubscription->current_month_paid;
        $dateOfTransaction = Carbon::now()->format('Y-m-d h:i:s');
        $daysDifference    = Carbon::parse($dateOfTransaction)->diffInDays($currentMonthPaid);

        if ($dateOfTransaction <= $currentMonthPaid && $daysDifference <= 10) 
        {
            return true;
        }

        return false;
    }

    public function isCurrentPaidMonthHasPassed()
    {
        $dateOfTransaction = Carbon::now()->format('Y-m-d h:i:s');
        $currentMonthPaid  = $this->companySubscription->current_month_paid;

        if ($dateOfTransaction > $currentMonthPaid) 
        {
            return true;
        }

        return false;
    }

    public function isAmountToPayGreatherThanAmountDue($amountToPay, $amountDue)
    {
        return ($amountToPay > $amountDue);
    }

    public function getPenaltyCharge()
    {
        $currentMonthRate = $this->companySubscription->rate;

        return round(($currentMonthRate / 100) * 5);
    }

    public function getDiscountedValue($percentage, $amountToBePercentage)
    {
        $discount = ($percentage / 100) * $amountToBePercentage;

        return round($discount, 2);
    }

    public function isPartialPayment($amountToPay, $amountToPaid)
    {
        return ($amountToPay < $amountToPaid && $amountToPay != 0);
    }

    public function hasCompanyDiscount()
    {
        return ($this->companyDetails->discount > 0);
    }

    public function updateCompanySubscription($id, $data)
    {
        return tap(CompanySubscriptions::where('company_id', $id))->update($data)->first();
    }

    public function createSubscriptionPayment($data)
    {
        return CompanySubscriptionPayments::create($data);
    }

    public function getCouponCodeDiscount($couponData, $mode, $subscriptionRatePrice)
    {
        if (!$couponData)
        {
            return 0;
        }
        else
        {
            if ($mode == 'monthly')
            {
                //! Coupon Discount
                if($couponData->type == 'fixed')
                {
                    return (float) $couponData->discount;
                }
                else
                {
                    return $this->getDiscountedValue($couponData->discount, $subscriptionRatePrice);
                }
            }
            else if ($mode == 'yearly')
            {
                if($couponData->type == 'fixed')
                {
                    return (float) $couponData->discount * 12;
                }
                else
                {
                    return  $this->getDiscountedValue($couponData->discount, $subscriptionRatePrice);
                }
            }

            return 0;
        }
    }
}
