<?php

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * Payment class stores and handles data about a certain payment
 *
 */
class DateBook_Payment_Class {

    /**
     * Payment id
     *
     * @access public
     * @var int
     */
    public $id;

    /**
     * User id
     *
     * @access public
     * @var int
     */
    public $user_id;

    /**
     * Subscription id
     *
     * @access public
     * @var int
     */
    public $subscription_id;

    /**
     * Plan price period
     *
     * @access public
     * @var int
     */
    public $plan_price_period;

    /**
     * Payment status
     *
     * @access public
     * @var string
     */
    public $status;

    /**
     * Payment date
     *
     * @access public
     * @var datetime
     */
    public $date;

    /**
     * Payment start_date
     *
     * @access public
     * @var datetime
     */
    public $start_date;

    /**
     * Payment expiration_date
     *
     * @access public
     * @var datetime
     */
    public $expiration_date;

    /**
     * Payment amount
     *
     * @access public
     * @var int
     */
    public $amount;

    /**
     * Currency code
     *
     * @access public
     * @var int
     */
    public $currency;

    /**
     * The payment type
     *
     * @access public
     * @var string
     *
     */
    public $type;

    /**
     * The payment gateway used to make the payment
     *
     * @access public
     * @var string
     *
     */
    public $payment_gateway;

    /**
     * The transaction id returned by the payment gateway
     *
     * @access public
     * @var string
     *
     */
    public $transaction_id;

    /**
     * The profile id returned by a payment gateway for a recurring profile/subscription
     *
     * @access public
     * @var string
     *
     */
    public $profile_id;

    /**
     * Error logs saved for the payment
     *
     * @access public
     * @var array
     *
     */
    public $logs;

    /**
     * User IP address
     *
     * @access public
     * @var string
     */
    public $ip_address;

    /**
     * Discount code
     *
     * @access public
     * @var int
     */
    public $discount_code;


    /**
     * Constructor
     *
     */
    public function __construct( $id = 0 ) {

        // Return if no id provided
        if( $id == 0 ) {
            $this->id = 0;
            return;
        }

        // Get payment data from the db
        $data = $this->get_data( $id );

        // Return if data is not in the db
        if( is_null($data) ) {
            $this->id = 0;
            return;
        }

        // Populate the data
        $this->set_instance( $data );

    }


    /**
     * Sets the object properties given an array of corresponding data
     *
     * Note: This method is not intended to be used outside of the plugin's core
     *
     * @param array $data
     *
     */
    public function set_instance( $data = array() ) {

		// Inconsistency fix between the db table row name and the DateBook_Payment_Class property
		if( empty( $data['subscription_id'] ) && !empty( $data['subscription_plan_id'] ) ){
			$data['subscription_id'] = $data['subscription_plan_id'];
		}

        // Grab all properties and populate them
        foreach( get_object_vars( $this ) as $property => $value ) {

            if( isset( $data[$property] ) ) {

                // The logs are saved as json in the db, we want them as an associative array
                if( $property == 'logs' )
                    $data[$property] = !empty( $data[$property] ) ? json_decode( $data[$property], ARRAY_A ) : '';

                // Empty dates overwrite
                if( $data[$property] == '0000-00-00 00:00:00' )
                    $data[$property] = '';

                $this->$property = $data[$property];

            }

        }

    }


    /**
     * Retrieve the row data for a given id
     *
     * @param int $id - the id of the payment we wish to get
     *
     * @return array
     *
     */
    public function get_data( $id ) {

        global $wpdb;
		
		$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

        $result = $wpdb->get_row("SELECT * FROM " . $table_name . " WHERE id = {$id}", ARRAY_A );

        return $result;

    }


    /**
     * Inserts payment data into the database
     *
     * @param array $data
     *
     * @return mixed        - int $payment_id or false if the row could not be added
     *
     */
    public function insert( $data = array() ) {

        global $wpdb;

        $defaults = array(
            'date'       => date('Y-m-d H:i:s'),
            'amount'     => 0,
            'status'     => 'pending',
			'transaction_id' => '',
			'logs' => '',
            'ip_address' => datebook_get_user_ip_address()
        );

        $data = wp_parse_args( $data, $defaults );
		
        // User ID and subscription plan ID are needed
        if( empty( $data['user_id'] ) || empty( $data['subscription_plan_id'] ) )
            return false;


        // Eliminate all values that are not a part of the object
        $object_vars = array_keys( get_object_vars( $this ) );

        foreach( $data as $key => $val ) {

            // Inconsistency fix between the db table row name and
            // the DateBook_Payment_Class property
            if( $key == 'subscription_plan_id' )
                $key = 'subscription_id';

            if( !in_array( $key, $object_vars ) )
                unset( $data[$key] );

        }
		
		$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

        // Insert payment
        $insert_result = $wpdb->insert( $table_name, $data );

        if( $insert_result ) {

            // Populate current object
            $this->id = $wpdb->insert_id;
            $this->set_instance( $data );

            return $this->id;

        }

        return false;

    }


    /**
     * Method to update any data of the payment
     *
     * @param array $data    - the new data
     *
     * @return bool
     *
     */
    public function update( $data = array() ) {

        global $wpdb;
		
		$table_payments = strtolower($wpdb->prefix) . 'datebook_payments';

        $update_result = $wpdb->update( $table_payments, $data, array( 'id' => $this->id ) );

        // Can return 0 if no rows are affected
        if( $update_result !== false )
            $update_result = true;

        return $update_result;

    }


    /**
     * Removes the payment from the database
     *
     */
    public function remove() {

        if( !$this->is_valid() )
            return false;

        global $wpdb;
		
		$table_payments = strtolower($wpdb->prefix) . 'datebook_payments';

        $remove_result = $wpdb->delete( $table_payments, array( 'id' => $this->id ) );

        // Can return 0 if no rows are affected
        if( $remove_result !== false )
            $remove_result = true;

        return $remove_result;

    }


    /**
     * Check to see if payment is saved in the db
     *
     */
    public function is_valid() {

        if( empty($this->id) )
            return false;
        else
            return true;

    }


    /**
     * Returns the array representation of the current object instance
     *
     */
    public function to_array() {

        return get_object_vars( $this );

    }


    /**
     * Add a log entry to the payment
     *
     * @param string $type      - the type of the log
     * @param string $message   - a human readable message
     * @param array $data       - an array of data saved from the payment gateway
     *
     * @return bool
     *
     */
    public function add_log_entry( $type = '', $message = '', $data = array() ) {

        global $wpdb;

        if( empty($type) || empty($message) || empty($data) )
            return false;
			
		$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

        $payment_logs = $wpdb->get_var( "SELECT logs FROM {$table_name} WHERE id LIKE {$this->id}" );

        if( $payment_logs == null )
            $payment_logs = array();
        else
            $payment_logs = json_decode( $payment_logs );

        $payment_logs[] = array(
            'type'      => $type,
            'message'   => $message,
            'data'      => $data
        );

        $update_result = $wpdb->update( $table_name, array( 'logs' => json_encode($payment_logs) ), array( 'id' => $this->id ) );

        if( $update_result!== false )
            $update_result = true;

        return $update_result;

    }


    /**
     * Method to add a new payment in the database
     *
     * @param int $user_id
     * @param string $status
     * @param datetime $date
     * @param int $amount
     * @param array $subscription_plan_ids
     *
     * @return mixed    - int $payment_id or false if the row could not be added
     *
     */
    public function add( $user_id, $status, $date, $amount, $subscription_plan_id ) {

        global $wpdb;
		
		$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

        $insert_result = $wpdb->insert( $table_name, array( 'user_id' => $user_id, 'subscription_plan_id' => $subscription_plan_id, 'status' => $status, 'date' => $date, 'amount' => $amount, 'ip_address' => datebook_get_user_ip_address() ) );

        if( $insert_result ) {
            $payment_id = $wpdb->insert_id;

            $this->id = $payment_id;

            return $payment_id;
        }

        return false;

    }


    /**
     * Method to update the status of the payment
     *
     * @param string $status    - the new status
     *
     * @return bool
     *
     */
    public function update_status( $status ) {

        global $wpdb;
		
		$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

        $update_result = $wpdb->update( $table_name, array( 'status' => $status ), array( 'id' => $this->id ) );

        // Can return 0 if no rows are affected
        if( $update_result !== false )
            $update_result = true;

        return $update_result;

    }


    /**
     * Method to update the type of the payment
     *
     * @param string $type    - the new status
     *
     * @return bool
     *
     */
    public function update_type( $type ) {

        global $wpdb;
		
		$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

        $update_result = $wpdb->update( $table_name, array( 'type' => $type ), array( 'id' => $this->id ) );

        // Can return 0 if no rows are affected
        if( $update_result !== false )
            $update_result = true;

        return $update_result;

    }


    /**
     * Method to update the profile id of the payment
     *
     * @param string $profile_id    - the new status
     *
     * @return bool
     *
     */
    public function update_profile_id( $profile_id ) {

        global $wpdb;
		
		$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

        $update_result = $wpdb->update( $table_name, array( 'profile_id' => $profile_id ), array( 'id' => $this->id ) );

        // Can return 0 if no rows are affected
        if( $update_result !== false )
            $update_result = true;

        return $update_result;

    }

}



/**
 * Wrapper function to return a payment object
 *
 * @param int $payment_id
 *
 */
function datebook_get_payment( $payment_id = 0 ) {

    return new DateBook_Payment_Class( $payment_id );

}



/**
 * Return payments filterable by an array of arguments
 *
 * @param array $args
 *
 * @return array
 *
 */
function datebook_get_payments( $args = array() ) {
	
    global $wpdb;
	
	$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

    $defaults = array(
        'order'         => 'DESC',
        'orderby'       => 'id',
        'number'        => 1000,
        'offset'        => '',
        'status'        => '',
        'type'          => '',
        'user_id'       => '',
        'profile_id'    => '',
        'date'          => '',
        'search'        => ''
    );

    $args = apply_filters( 'datebook_get_payments_args', wp_parse_args( $args, $defaults ), $args, $defaults );

    // Start query string
    $query_string       = "SELECT datebook_payments.* ";

    // Query string sections
    $query_from         = "FROM {$table_name} datebook_payments ";
    $query_inner_join   = "INNER JOIN {$wpdb->users} users ON datebook_payments.user_id = users.id ";
    $query_where        = "WHERE 1=%d ";

    // Add search query
    if( !empty($args['search']) ) {
        $search_term    = sanitize_text_field( $args['search'] );
        $query_where    = $query_where . " AND " . " ( datebook_payments.transaction_id LIKE '%s' OR users.user_nicename LIKE '%%%s%%' OR posts.post_title LIKE '%%%s%%' ) ". " ";
    }

    // Filter by status
    if( !empty( $args['status'] ) ) {
        $status         = sanitize_text_field( $args['status'] );
        $query_where    = $query_where . " AND " . " datebook_payments.status LIKE '{$status}'";
    }

    /*
     * Filter by date
     * Can be filtered by - a single date that will return payments from that date
     *                    - an array with two dates that will return payments between the two dates
     */
    if( !empty( $args['date'] ) ) {

        if( is_array( $args['date'] ) && !empty( $args['date'][0] ) && !empty( $args['date'][1] ) ) {

            $args['date'][0] = sanitize_text_field( $args['date'][0] );
            $args['date'][1] = sanitize_text_field( $args['date'][1] );

            $query_where = $query_where . " AND " . " ( datebook_payments.date BETWEEN '{$args['date'][0]}' AND '{$args['date'][1]}' )";

        } elseif( is_string( $args['date'] ) ) {

            $args['date'] = sanitize_text_field( $args['date'] );

            $query_where = $query_where . " AND " . " datebook_payments.date LIKE '%%{$args['date']}%%'";

        }

    }

    // Filter by type
    if( !empty( $args['type'] ) ) {
        $type = sanitize_text_field( $args['type'] );
        $query_where    = $query_where . " AND " . " datebook_payments.type LIKE '{$type}'";
    }

    // Filter by profile_id
    if( !empty( $args['profile_id'] ) ) {
        $profile_id = sanitize_text_field( $args['profile_id'] );
        $query_where    = $query_where . " AND " . " datebook_payments.profile_id LIKE '{$profile_id}'";
    }

    // Filter by profile id
    if( !empty( $args['user_id'] ) ) {
        $user_id = (int)trim( $args['user_id'] );
        $query_where    = $query_where . " AND " . " datebook_payments.user_id = {$user_id}";
    }

    $query_order_by = '';
    if ( !empty($args['orderby']) )
        $query_order_by = " ORDER BY datebook_payments." . sanitize_text_field( $args['orderby'] ) . ' ';

    $query_order = strtoupper( sanitize_text_field( $args['order'] ) ) . ' ';

    $query_limit        = '';
    if( $args['number'] )
        $query_limit    = 'LIMIT ' . (int)trim( $args['number'] ) . ' ';

    $query_offset       = '';
    if( $args['offset'] )
        $query_offset   = 'OFFSET ' . (int)trim( $args['offset'] ) . ' ';

    // Concatenate query string
    $query_string .= $query_from . $query_inner_join . $query_where . $query_order_by . $query_order . $query_limit . $query_offset;
	
	
    // Return results
    if (!empty($search_term))
        $data_array = $wpdb->get_results( $wpdb->prepare( $query_string, 1, $wpdb->esc_like( $search_term ) , $wpdb->esc_like( $search_term ), $wpdb->esc_like( $search_term ) ), ARRAY_A );
    else
        $data_array = $wpdb->get_results( $wpdb->prepare( $query_string, 1 ), ARRAY_A );

    $payments = array();

    if( !empty( $data_array ) ) {
        foreach( $data_array as $key => $data ) {

            // Inconsistency fix between the db table row name and
            // the DateBook_Payment_Class property
            if( !empty( $data['subscription_plan_id'] ) )
                $data['subscription_id'] = $data['subscription_plan_id'];

            $payment = new DateBook_Payment_Class();
            $payment->set_instance( $data );

            $payments[] = $payment;
        }
    }

    /**
     * Filter payments just before returning them
     *
     * @param array $payments - the array of returned payments from the db
     * @param array $args     - the arguments used to query the payments from the db
     *
     */
    $payments = apply_filters( 'datebook_get_payments', $payments, $args );

    return $payments;

}


/**
 * Returns the metadata for a given payment
 *
 * @param int    $payment_id
 * @param string $meta_key
 * @param bool   $single
 *
 * @return mixed - single metadata value | array of values
 *
 */
function datebook_get_payment_meta( $payment_id = 0, $meta_key = '', $single = false ) {

    return get_metadata( 'payment', $payment_id, $meta_key, $single );

}


/**
 * Adds the metadata for a payment
 *
 * @param int    $payment_id
 * @param string $meta_key
 * @param string $meta_value
 * @param bool   $unique
 *
 * @return mixed - int | false
 *
 */
function datebook_add_payment_meta( $payment_id = 0, $meta_key = '', $meta_value = '', $unique = false ) {

    return add_metadata( 'payment', $payment_id, $meta_key, $meta_value, $unique );

}


/**
 * Updates the metadata for a payment
 *
 * @param int    $payment_id
 * @param string $meta_key
 * @param string $meta_value
 * @param string $prev_value
 *
 * @return bool
 *
 */
function datebook_update_payment_meta( $payment_id = 0, $meta_key = '', $meta_value = '', $prev_value = '' ) {

    return update_metadata( 'payment', $payment_id, $meta_key, $meta_value, $prev_value );

}


/**
 * Deletes the metadata for a payment
 *
 * @param int    $payment_id
 * @param string $meta_key
 * @param string $meta_value
 * @param string $delete_all - If true, delete matching metadata entries for all payments, ignoring 
 *                             the specified payment_id. Otherwise, only delete matching metadata entries 
 *                             for the specified payment_id.
 *
 */
function datebook_delete_payment_meta( $payment_id = 0, $meta_key = '', $meta_value = '', $delete_all = false ) {

    return delete_metadata( 'payment', $payment_id, $meta_key, $meta_value, $delete_all );

}


/**
 * Returns the total number of payments from the db
 *
 * @param array $args  - array of arguments to filter the count for
 *
 * @return int
 *
 */
function datebook_get_payments_count( $args = array() ) {

    global $wpdb;
	
	$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

    /**
     * Base query string
     */
    $query_string = "SELECT COUNT(datebook_payments.id) FROM {$table_name} datebook_payments ";

    /**
     * Inner join
     */
    $query_inner_join = '';

    if( !empty( $args['search'] ) ) {
        $query_inner_join  = "INNER JOIN {$wpdb->users} users ON datebook_payments.user_id = users.id ";
        $query_inner_join .= "INNER JOIN {$wpdb->posts} posts ON datebook_payments.subscription_plan_id = posts.id ";
    }

    /**
     * Where clauses
     */
    $query_where  = "WHERE 1=%d ";

    // Filter by search
    if( !empty( $args['search'] ) ) {
        $search = sanitize_text_field( $args['search'] );
        $query_where .= " AND ( datebook_payments.transaction_id LIKE '%%{$search}%%' OR users.user_nicename LIKE '%%{$search}%%' OR posts.post_title LIKE '%%{$search}%%' ) ". " ";
    }

    // Filter by status
    if( !empty( $args['status'] ) ) {
        $status = sanitize_text_field( $args['status'] );
        $query_where .= "AND datebook_payments.status = '{$status}' ";
    }


    /**
     * Get cached version first
     *
     */
    $key   = md5( 'datebook_payments_count_' . serialize( $args ) );
    $count = get_transient( $key );


    /**
     * Make db query if cache is empty and set the cache
     *
     */
    if( false === $count ) {

        $count = $wpdb->get_var( $wpdb->prepare( $query_string . $query_inner_join . $query_where, 1 ) );

        /**
         * The expiration time ( in seconds ) for the cached payments count returned for
         * the given args
         *
         * @param array $args
         *
         */
        $cache_time = apply_filters( 'datebook_payments_count_cache_time', 1800, $args );

        set_transient( $key, $count, $cache_time );

    }

    return (int)$count;

}


/**
 * Returns the number of payments a user has made
 *
 * @param int $user_id
 *
 * @return int
 *
 */
function datebook_get_member_payments_count( $user_id = 0 ) {

    if( $user_id === 0 )
        return 0;

    global $wpdb;

	$table_name = strtolower($wpdb->prefix) . 'datebook_payments';

    $user_id = (int)$user_id;

    $query_string = "SELECT COUNT( DISTINCT id ) FROM {$table_name} WHERE 1=%d AND user_id LIKE {$user_id}";

    $count = $wpdb->get_var( $wpdb->prepare( $query_string, 1 ) );

    return (int)$count;

}


/**
 * Function that returns all possible payment statuses
 *
 * @return array
 *
 */
function datebook_get_payment_statuses() {

    $payment_statuses = array(
        'pending'   => esc_html__( 'Pending', 'datebook' ),
        'completed' => esc_html__( 'Completed', 'datebook' ),
        'refunded'  => esc_html__( 'Refunded', 'datebook' )
    );

    /**
     * Filter to add/remove payment statuses
     *
     * @param array $payment_statuses
     *
     */
    $payment_statuses = apply_filters( 'datebook_payment_statuses', $payment_statuses );

    return $payment_statuses;

}


/**
 * Returns an array with the payment types supported
 *
 * @return array
 *
 */
function datebook_get_payment_types() {

	$payment_types = array(
		'bank_payment'						=> esc_html__( 'Bank Payment', 'datebook' ),
		'web_accept_paypal_standard'		=> esc_html__( 'PayPal Standard - One-Time Payment', 'datebook' ),
		'free_payment'						=> esc_html__( 'Free Activation', 'datebook' ),

		'subscription_initial_payment'		=> esc_html__( 'Subscription Initial Payment', 'datebook' ),
		'subscription_recurring_payment'	=> esc_html__( 'Subscription Recurring Payment', 'datebook' ),
		'subscription_renewal_payment'		=> esc_html__( 'Subscription Renewal Payment', 'datebook' ),
		'subscription_upgrade_payment'		=> esc_html__( 'Subscription Upgrade Payment', 'datebook' ),

		'subscription_woocommerce_payment'	=> esc_html__( 'WooCommerce Payment', 'datebook' ),
    );

    /**
     * Filter to add/remove payment types
     *
     * @param array $payment_types
     *
     */
    $payment_types = apply_filters( 'datebook_payment_types', $payment_types );

    return $payment_types;

}


/**
 * Returns true if the test mode is checked in the payments settings page
 * and false if it is not checked
 *
 * @return bool
 *
 */
function datebook_is_payment_test_mode() {

    $datebook_settings = get_option('datebook_settings');

    if( isset( $datebook_settings['payments']['test_mode'] ) && $datebook_settings['payments']['test_mode'] == 1 )
        return true;
    else
        return false;

}


/*
 * Returns the name of the payment type given its slug
 *
 * @param string $payment_type_slug
 *
 * @return string
 *
 */
function datebook_get_payment_type_name( $payment_type_slug ) {

    $payment_types = datebook_get_payment_types();

    if( isset( $payment_types[$payment_type_slug] ) )
        return $payment_types[$payment_type_slug];
    else
        return '';

}


/**
 * Processes payments for custom member subscriptions
 * Is a callback to the cron job with the same name
 *
 */
function datebook_cron_process_member_subscriptions_payments() {

    $args = array(
        'status'                      => 'active',
        'billing_next_payment_after'  => date( 'Y-m-d H:i:s', time() - 1 * MONTH_IN_SECONDS ),
        'billing_next_payment_before' => date( 'Y-m-d H:i:s' )
    );

    $subscriptions = datebook_get_member_subscriptions( $args );

    $settings          = get_option( 'datebook_settings' );
    $payments_settings = $settings['payments'];

    foreach( $subscriptions as $subscription ) {

        if( empty( $subscription->payment_gateway ) )
            continue;

        $payment_gateway = DateBook_Profile_Subscription::datebook_get_payment_gateway( $subscription->payment_gateway );

        if( ! method_exists( $payment_gateway, 'process_payment' ) )
            continue;

        // Payment data
        $payment_data = apply_filters( 'datebook_cron_process_member_subscriptions_payment_data' ,
            array(
                'user_id'              => $subscription->user_id,
                'subscription_plan_id' => $subscription->subscription_plan_id,
                'date'                 => date( 'Y-m-d H:i:s' ),
				'start_date'           => $subscription->start_date,
				'expiration_date'      => $subscription->expiration_date,
                'amount'               => $subscription->billing_amount,
                'payment_gateway'      => $subscription->payment_gateway,
                'currency'             => ( isset( $payments_settings['currency'] ) ? $payments_settings['currency'] : 'USD' ),
                'status'               => 'pending',
                'type'                 => 'subscription_recurring_payment'
            ),
            $subscription
        );

        $payment = new DateBook_Payment_Class();
        $payment->insert( $payment_data );

        // Process payment
        $response = $payment_gateway->process_payment( $payment->id, $subscription->id );

        if( $response ) {

            $subscription_data = array(
                'status'               => 'active',
                'billing_last_payment' => date( 'Y-m-d H:i:s' )
            );

            // Set the next billing date
            if( ! empty( $subscription->billing_duration ) ) {

                $next_payment = date( 'Y-m-d H:i:s', strtotime( "+" . $subscription->billing_duration . " " . $subscription->billing_duration_unit ) );

                $subscription_data['billing_next_payment'] = $next_payment;

            }else{
                //here I think we should treat the non auto recurring with free trial cases
                $subscription_data['billing_next_payment'] = null;
            }

        } else {

            $subscription_data = array(
                'status' => 'expired'
            );

        }

        $subscription->update( $subscription_data );

    }

}
add_action( 'datebook_cron_process_member_subscriptions_payments', 'datebook_cron_process_member_subscriptions_payments' );