<?php

namespace WPAdverts\Stripe;

class Ajax {
    
    /**
     * Constructor
     * 
     * Registers AJAX actions
     * 
     * @since 1.0
     * @return void
     */
    public function __construct() {
        add_action('wp_ajax_wpadverts-stripe-card-trash', array( $this, "trash" ) );
        add_action('wp_ajax_wpadverts-stripe-card-source', array( $this, "source" ) );
        add_action('wp_ajax_wpadverts-stripe-complete', array( $this, "complete" ) );
        add_action('wp_ajax_wpadverts-stripe-webhook', array( $this, "webhook" ) );
        
        add_action('wp_ajax_nopriv_wpadverts-stripe-complete', array( $this, "complete" ) );
        add_action('wp_ajax_nopriv_wpadverts-stripe-webhook', array( $this, "webhook" ) );
    }
    
    public function load_stripe() {
        if( ! class_exists( "\Stripe\Stripe" ) ) {
            include_once Core::get_instance()->get_basedir() . '/stripe/init.php';
        }
    }
    
    /**
     * Prints response in the browser
     * 
     * @since 1.0
     * @since 1.2   $code param
     * 
     * @param int    $status    Returns response code (0 or 1)
     * @param string $message   Response message
     * @param int    $code      Response code
     * @return void
     */
    protected function _print( $status, $message, $code = 200 ) {
        
        if( $code !== 200 ) {
            http_response_code( $code );
        }
        
        $json = json_encode( apply_filters( "wpadverts_stripe_ajax_print", array( 
            "result" => $status, 
            "message" =>  $message
        ) ) );


        echo $json;
        exit;
    }
    
    /**
     * Prints response to screen if $condition is true
     * 
     * @since 1.0
     * @param boolean   $condition     Condition to check
     * @param int       $status        Returns response code (0 or 1)
     * @param string    $message       Response message
     * @return void
     */
    protected function _print_if( $condition, $status, $message ) {
        if( $condition ) {
            $this->_print( $status, $message );
        }
    }
    
    /**
     * Add additional data to print output.
     * 
     * This function is executed using wpadverts_stripe_ajax_print filter registered in
     * self::complete() function
     * 
     * @see     wpadverts_stripe_ajax_print filter
     * @see     self::complete()
     * 
     * @since   1.2.0
     * @param   array $data   Data to print
     * @return  array
     */
    public function print_complete( $data ) {
        $data["intent_id"] = adverts_request( "payment_intent_id" );
        $data["index"] = absint( adverts_request( "index" ) ) + 1;
        return $data;
    }
    
    /**
     * Trash credit card action
     * 
     * This function is called when user in third step in [adverts_add] tries
     * to delete a credit card.
     * 
     * This function is called by wp_ajax_wpadverts-stripe-card-trash action 
     * 
     * @since 1.0
     * @return void
     */
    public function trash() {
        if( ! check_ajax_referer( 'wpadverts-stripe-cc-nonce', 'nonce', false ) ) {
            $this->_print( 0, __( "Invalid Session. Please refresh the page and try again.", "adverts" ) );
        }
        
        $this->load_stripe();
        
        \Stripe\Stripe::setApiKey( adverts_config( 'stripe.secret_key' ) );
        
        $this->_print_if( !get_current_user_id(), 0, __( "You need to be logged in to delete cards.", "wpadverts-stripe" ) );
        
        $customer_id = get_user_meta( get_current_user_id(), Core::get_customer_meta_key_name(), true );
        $card_id = adverts_request( "card_id" );
        
        $this->_print_if( !$customer_id, 0, __( "Unknown Stripe User ID", "wpadverts-stripe" ) );
        $this->_print_if( !$card_id, 0, __( "You need to select card you want to delete.", "wpadverts-stripe" ) );
        
        try {
            $customer = \Stripe\Customer::retrieve( $customer_id );
            
            if( stripos( $card_id, "pm_" ) === 0 ) {
                $payment_method = \Stripe\PaymentMethod::retrieve( $card_id );
                $payment_method->detach();
                $deleted = true;
            } else {
                $card = $customer->sources->retrieve( $card_id )->delete();
                $deleted = $card->deleted;
            }

            if( $deleted === true ) {
                $this->_print( 1, __( "Card deleted", "wpadverts-stripe" ) );
            } else {
                $this->_print( 0, __( "Card count not be deleted.", "wpadverts-stripe" ) );
            }
        } catch(\Exception $e) {
            $this->_print( 0, $e->getMessage() );
        }
    }
    
    /**
     * Select default credit card action
     * 
     * This function is called when user in third step in [adverts_add] tries
     * to select a default credit card.
     * 
     * This function is called by wp_ajax_wpadverts-stripe-stripe-card-source action 
     * 
     * @since 1.0
     * @return void
     */
    public function source() {
        if( ! check_ajax_referer( 'wpadverts-stripe-cc-nonce', 'nonce', false ) ) {
            $this->_print( 0, __( "Invalid Session. Please refresh the page and try again.", "adverts" ) );
        }
        
        $this->load_stripe();
        
        \Stripe\Stripe::setApiKey( adverts_config( 'stripe.secret_key' ) );
        
        $this->_print_if( !get_current_user_id(), 0, __( "You need to be logged in to delete cards.", "wpadverts-stripe" ) );
        
        $customer_id = get_user_meta( get_current_user_id(), Core::get_customer_meta_key_name(), true );
        $card_id = adverts_request( "card_id" );
        
        $this->_print_if( !$customer_id, 0, __( "Unknown Stripe User ID", "wpadverts-stripe" ) );
        $this->_print_if( !$card_id, 0, __( "You need to select card you want to delete.", "wpadverts-stripe" ) );

        try {
            if( stripos( $card_id, "pm_" ) === 0 ) {
                
                $customer = \Stripe\Customer::retrieve( $customer_id );
                $customer->invoice_settings->default_payment_method = $card_id;
                $customer->save();
            } else {
                
                $customer = \Stripe\Customer::retrieve( $customer_id );
                $customer->default_source = $card_id;
                $customer->invoice_settings->default_payment_method = null;
                $customer->save();
            }
            
            $this->_print( 1, __( "New default card set", "wpadverts-stripe" ) );

        } catch(\Exception $e) {
            $this->_print( 0, $e->getMessage() );
        }
    }
    
    /**
     * Complete payment action
     * 
     * This function is called after Stripe payment and checks if the payment
     * was confirmed by Stripe.
     * 
     * The function can also use \Stripe\PaymentIntent::retrieve() function to
     * confirm the payment.
     * 
     * This function is called by:
     * - wp_ajax_wpadverts-stripe-stripe-card-source action 
     * - wp_ajax_nopriv_wpadverts-stripe-stripe-card-source action 
     * 
     * @since 1.1
     * @return void
     */
    public function complete() {
        
        $this->load_stripe();
        
        \Stripe\Stripe::setApiKey( adverts_config( 'stripe.secret_key' ) );

        $err1 = __( "Payment Intent %s does not exist.", "wpadverts-stripe" );
        
        $intent_id = adverts_request( "payment_intent_id" );
        
        $posts = get_posts( array(
            "post_type" => "adverts-payment",
            "post_status" => "any",
            "meta_key" => "_adverts_payment_intent_id",
            "meta_value" => adverts_request( "payment_intent_id" )
        ) );
        
        $this->_print_if( ! isset( $posts[0] ), sprintf( $err1, $intent_id ), 400 );
        
        $payment = $posts[0];
        
        if( $payment->post_status == "pending" && adverts_config( "stripe.live_confirm" ) == 1 ) {
            
            // try non-secure payment verification ...
            $intent = \Stripe\PaymentIntent::retrieve( $intent_id );
            $dec = adverts_config( 'config.currency_decimals' );
            
            if( $intent->status == "succeeded" ) {
                $this->maybe_save_cc( $payment );
                
                wp_update_post( array( 
                    "ID" => $payment->ID,
                    "post_status" => "completed",
                ) );
                
                $payment_id = $payment->ID;
                $old_status = "pending";
                $new_status = "completed";
                
                update_post_meta( $payment->ID, '_adverts_payment_paid', round( $intent->amount / pow( 10, $dec ), $dec ) );
                
                do_action( "adverts_payment_status_change", get_post( $payment_id ), $new_status, $old_status );
                do_action( "adverts_payment_{$new_status}", get_post( $payment_id ) );
                do_action( "adverts_payment_{$old_status}_to_{$new_status}", get_post( $payment_id ) );
                
                adext_payments_log( $payment->ID, __( "Payment verified using live confirmation.", "wpadverts-stripe" ) );
                
                $payment = get_post( $payment->ID );
            } else if( $intent->status == "failed" ) {
                
                wp_update_post( array( 
                    "ID" => $payment->ID,
                    "post_status" => "failed",
                ) );
                
                $payment_id = $payment->ID;
                $old_status = "pending";
                $new_status = "failed";
                
                do_action( "adverts_payment_status_change", get_post( $payment_id ), $new_status, $old_status );
                do_action( "adverts_payment_{$new_status}", get_post( $payment_id ) );
                do_action( "adverts_payment_{$old_status}_to_{$new_status}", get_post( $payment_id ) );
                
                adext_payments_log( $payment->ID, __( "Payment failed - checked using live confirmation.", "wpadverts-stripe" ) );
                
                $payment = get_post( $payment->ID );
            }
        }
        
        add_filter( "wpadverts_stripe_ajax_print", array( $this, "print_complete" ) );
        
        if( $payment->post_status == "completed" ) {
            $this->_print( 1, $this->get_success_message( array( "payment_id" => $payment->ID ) ) );
        } else if( absint( adverts_request( "index" ) ) >= 10 ) {
            $m = sprintf( __( "No payment confirmation received. If the payment was deducted from your credit card please contact website administrator. Transaction ID %s", "wpadverts-stripe" ), $intent_id ) ;
            $this->_print( -1, $this->get_html_message( $m, "error", "adverts-icon-cancel" ) );
        } else if( $payment->post_status == "pending" ) {
            $m = __( "Waiting for credit card payment confirmation. This may take up to a minute.", "wpadverts-stripe" );
            $this->_print( 0, $this->get_html_message( $m, "pending", "adverts-icon-spinner animate-spin" ) );
        } else if( $payment->post_status == "failed" ) {
            $m = sprintf( __( "Payment Failed. Please contact website administrator for more details. Transaction ID %s", "wpadverts-stripe" ), $intent_id );
            $this->_print( -2, $this->get_html_message( $m, "error", "adverts-icon-cancel" ) );
        } else {
            $this->_print( -3, __( "Unknown", "wpadverts-stripe" ) );
        }
    } 
    
    /**
     * Stripe Webhook Endpoint
     * 
     * This function is called by Stripe.com when Stripe sends a webhook 
     * to page /wp-admin/admin-ajax?action=wpadverts-stripe-webhook.
     * 
     * The function will try to run one of the registered events.
     * 
     * This function is called by:
     * - wp_ajax_wpadverts-stripe-webhook action 
     * - wp_ajax_nopriv_wpadverts-stripe-webhook action 
     * 
     * @since 1.1
     * @return void
     */
    public function webhook() {
        
        $this->load_stripe();
        \Stripe\Stripe::setApiKey( adverts_config( 'stripe.secret_key' ) );

        $endpoint_secret = adverts_config( "stripe.payment_intent__succeeded" );
        $payload = @file_get_contents('php://input');
        $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
        $event = null;

        try {
            $event = \Stripe\Webhook::constructEvent(
                $payload, $sig_header, $endpoint_secret
            );
        } catch(\UnexpectedValueException $e) {
            // Invalid payload
            http_response_code(400);
            exit();
        } catch(\Stripe\Error\SignatureVerification $e) {
            // Invalid signature
            http_response_code(400);
            exit();
        }
        
        // Handle the event
        switch ($event->type) {
            case 'payment_intent.succeeded':
                $paymentIntent = $event->data->object; // contains a StripePaymentIntent
                $this->payment_succeeded( $paymentIntent );
                http_response_code(200);
                exit();
                break;
            default:
                http_response_code(400);
                exit();
        }

        
    }
    
    /**
     * payment_succeeded Webhook Endpoint
     * 
     * This function is called by self::webhook() when Stripe.com submits a
     * payment_intent.succeeded event.
     * 
     * The function will either "complete" or "fail" the payment based on
     * PaymentIntent submitted by Stripe.
     * 
     * @since 1.1
     * @return void
     */
    public function payment_succeeded( $intent ) {
        
        $err1 = __( "Payment Intent %s does not exist.", "wpadverts-stripe" );
        $err2 = __( "Payment Intent %s returned status %s.", "wpadverts-stripe" );
        
        $posts = get_posts( array(
            "post_type" => "adverts-payment",
            "post_status" => "pending",
            "meta_key" => "_adverts_payment_intent_id",
            "meta_value" => $intent->id
        ) );
        
        $this->_print_if( ! isset( $posts[0] ), sprintf( $err1, $intent->id ), 400 );
        $this->_print_if( $intent->status !== "succeeded", sprintf( $err2, $intent->id, $intent->status ), 400 );
        
        $payment = $posts[0];

        $this->maybe_save_cc( $payment );
        $dec = adverts_config( 'config.currency_decimals' );
        
        wp_update_post( array( 
            "ID" => $payment->ID,
            "post_status" => "completed",
        ) );

        $payment_id = $payment->ID;
        $old_status = "pending";
        $new_status = "completed";

        update_post_meta( $payment->ID, '_adverts_payment_paid', round( $intent->amount / pow( 10, $dec ), $dec ) );

        do_action( "adverts_payment_status_change", get_post( $payment_id ), $new_status, $old_status );
        do_action( "adverts_payment_{$new_status}", get_post( $payment_id ) );
        do_action( "adverts_payment_{$old_status}_to_{$new_status}", get_post( $payment_id ) );
        
        adext_payments_log( $payment->ID, sprintf( __( "Payment Intent [%s] successfully verified.", "wpadverts-stripe" ), $intent->id ) );
        
        $this->_print( 1, __( "Payment Verified and Approved", "wpadverts-stripe" ) );
    }

    /**
     * Saves credit card in user meta
     * 
     * The function checks if the user requested saving credit card information
     * and if so a new payment method is assigned to the user.
     * 
     * If the current user does not have his stripe customer id then the customer
     * is created and his id is saved in the database.
     * 
     * @since   1.1.0
     * @param   WP_Post   $payment    Payment Object
     * @return  void
     */
    public function maybe_save_cc( $payment ) {
        
        if( get_post_meta( $payment->ID, "_wpadverts_stripe_save_cc", true ) == "pending" ) {
            
            $customer_id = get_user_meta( $payment->post_author, Core::get_customer_meta_key_name(), true );
            
            if( ! $customer_id ) {
                $customer = \Stripe\Customer::create([
                    'email' => get_post_meta( $payment->ID, 'adverts_email', true ),
                    'description' => get_post_meta( $payment->ID, 'adverts_person', true ),
                ]);

                $customer_id = $customer->id;

                update_user_meta( get_current_user_id(), Core::get_customer_meta_key_name(), $customer_id );
            } else {
                $customer = \Stripe\Customer::retrieve( $customer_id );
            }
 
            $intent_id = get_post_meta( $payment->ID, "_adverts_payment_intent_id", true );
            $intent = \Stripe\PaymentIntent::retrieve( $intent_id );
            
            $payment_method = \Stripe\PaymentMethod::retrieve( $intent->payment_method );
            $payment_method->attach( array( 'customer' => $customer_id ) );
            
            if( empty( $customer->invoice_settings->default_payment_method ) ) {
                \Stripe\Customer::update( $customer_id , array(
                    'invoice_settings' => array(
                        'default_payment_method' => $payment_method->id
                    )
                ) );
            }
        }
    } 
    
    /**
     * Returns HTML formatted message
     * 
     * The returned message will be used in [adverts_add] third step.
     * 
     * @since   1.1.0
     * @param   string    $text     Message to display
     * @param   string    $type     Message type; one of: success, warning, error.
     * @param   string    $icon     Adverts icon (a string starting with 'adverts-icon-')
     * @return  string              Formatted HTML message
     */
    public function get_html_message( $text, $type, $icon ) {
            $response = sprintf( '<div class="wpadverts-stripe-payment-%s wpadverts-stripe-payment-box">', $type );
            $response.= sprintf( '<div><span class="wpadverts-stripe-payment-box-icon %s"></span></div>', $icon );
            $response.= '<div class="wpadverts-stripe-payment-box-text"><p>' . $text . '</p></div>';
            $response.= '</div>';

            return $response;
    }
    
    /**
     * Returns HTML success message
     * 
     * The returned message will be used in [adverts_add] third step.
     * 
     * @since   1.1.0
     * @param   array   $data   Payment data
     * @return  string          HTML formatted success message
     */
    public function get_success_message( $data ) {
        $object_id = get_post_meta( $data['payment_id'], "_adverts_object_id", true );
        $advert = get_post( $object_id );
        
        if( $advert->post_status == "publish" ) {
            $m = __( 'Your Ad has been published. You can see it <a href="%s">here</a>.', "wpadverts-stripe" );
            $m = sprintf( $m, get_permalink( $advert->ID ) );
        } else {
            $m = __( 'Your Ad is pending. It will be published once an Administrator will approve it.', 'wpadverts-stripe' );
        }
        
        $response = '<div class="wpadverts-stripe-payment-success wpadverts-stripe-payment-box">';
        $response.= '<div><span class="wpadverts-stripe-payment-box-icon adverts-icon-ok"></span></div>';
        $response.= '<div class="wpadverts-stripe-payment-box-text"><p>' . $m . '</p></div>';
        $response.= '</div>';
        
        return $response;
    }
}
