<?php

if ( !defined( 'ABSPATH' ) ) {

    exit;
}

if ( !class_exists( 'Pricely_Pipeline_Coupons_Data' ) ) {

    class Pricely_Pipeline_Coupons_Data {

        private static $instance;
        private $pipeline_coupons_discounts;
        private $coupon_type;
        private $prev_totals;
        private $discount_array_amount_list;
        private $coupon_data_list;
        private $coupon_amounts_list;
        private $is_internal_call;
        private $external_coupon_ids;
        private $internal_coupon_ids;
        private $currency;

        public static function get_instance(): self {

            if ( !self::$instance ) {

                self::$instance = new self();
            }

            return self::$instance;
        }

        public function __construct() {

            $this->pipeline_coupons_discounts = Pricely_Pipeline_Coupons_Discounts::get_instance();

            $this->coupon_type = 'pricely_coupon';
            $this->prev_totals = array();
            $this->discount_array_amount_list = array();
            $this->coupon_data_list = array();
            $this->coupon_amounts_list = array();
            $this->is_internal_call = false;
            $this->external_coupon_ids = array();
            $this->internal_coupon_ids = array();

            add_filter( 'woocommerce_get_shop_coupon_data', array( $this, 'get_data' ), 99999, 3 );
            add_filter( 'woocommerce_coupon_get_amount', array( $this, 'get_amount' ), 99999, 2 );
            add_filter( 'woocommerce_coupon_get_discount_amount', array( $this, 'get_discount_amount' ), 99999, 5 );
            add_filter( 'woocommerce_coupon_custom_discounts_array', array( $this, 'get_discounts_array' ), 99999, 2 );
            add_filter( 'woocommerce_coupon_get_apply_quantity', array( $this, 'get_apply_quantity' ), 99999, 4 );
            add_filter( 'woocommerce_coupon_get_individual_use', array( $this, 'get_individual_use' ), 99999, 2 );
            add_filter( 'woocommerce_coupon_get_product_ids', array( $this, 'get_product_ids' ), 99999, 2 );
            add_filter( 'woocommerce_coupon_is_valid_for_product', array( $this, 'get_is_valid_for_product' ), 99999, 4 );
            add_filter( 'woocommerce_coupon_get_excluded_product_ids', array( $this, 'get_excluded_product_ids' ), 99999, 2 );
            add_filter( 'woocommerce_coupon_get_product_categories', array( $this, 'get_product_categories' ), 99999, 2 );
            add_filter( 'woocommerce_coupon_get_excluded_product_categories', array( $this, 'get_excluded_product_categories' ), 99999, 2 );
            add_filter( 'woocommerce_coupon_get_exclude_sale_items', array( $this, 'get_exclude_sale_items' ), 99999, 2 );
            add_filter( 'woocommerce_get_coupon_id_from_code', array( $this, 'get_coupon_id_from_code' ), 99999, 3 );
        }

        public function get_data( $coupon_data, $coupon_code, $coupon ) {

            $this->maybe_set_external_id( $coupon_code, $coupon_data );

            if ( $this->has_external_id( $coupon_code ) ) {

                return $coupon_data;
            }

            if ( false !== $coupon_data ) {

                return $coupon_data;
            }

            $data = $this->get_coupon_data( $coupon_code );

            if ( !$data ) {

                return $coupon_data;
            }

            $this->coupon_amounts_list[ $coupon_code ] = $data[ 'amount' ];

            $this->add_internal_id( $data[ 'code' ], $data[ 'id' ] );

            return $data;
        }

        public function get_amount( $coupon_amount, $coupon ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !isset( $this->coupon_amounts_list[ $coupon_code ] ) ) {

                return $coupon_amount;
            }

            return $this->coupon_amounts_list[ $coupon_code ];
        }

        public function get_discount_amount( $discount, $discounting_amount, $cart_item, $is_single, $coupon ) {

            $item_data = $this->get_item_data( $cart_item );

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                $this->set_prev_totals( $discount, '', $item_data, $discounting_amount, $is_single, true );

                return $discount;
            }

            $use_cache = true;

            if ( isset( $this->prev_totals[ 'coupons' ][ $coupon_code ] ) ) {

                $use_cache = false;
            }

            $discount_data = $this->get_discount_data( $coupon_code, $use_cache );

            if ( !$discount_data ) {

                $this->set_prev_totals( $discount, $coupon_code, $item_data, $discounting_amount, $is_single, true );

                return $discount;
            }


            $item_key = $item_data[ 'key' ];

            if ( !isset( $discount_data[ 'totals' ][ $item_key ] ) ) {

                $this->set_prev_totals( 0, $coupon_code, $item_data, $discounting_amount, $is_single, true );
                $this->set_discount_array_amount( 0, $coupon_code, $item_data );

                return 0;
            }

            $total = $discount_data[ 'totals' ][ $item_key ];

            $this->set_discount_array_amount( $total, $coupon_code, $item_data );

            if ( !$is_single ) {

                $this->set_prev_totals( $total, $coupon_code, $item_data, $discounting_amount, $is_single, false );

                return $this->convert_amount( $total, 'pipeline-get-coupon-discount-amount-' . $coupon_code );
            }

            $qty = $item_data[ 'quantity' ];

            $multi_totals = $total / $qty;

            $this->set_prev_totals( $multi_totals, $coupon_code, $item_data, $discounting_amount, $is_single, false );

            return $this->convert_amount( $multi_totals, 'pipeline-get-coupon-discount-amount-single-' . $coupon_code );
        }

        public function get_discounts_array( $discounts, $coupon ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $discounts;
            }

            if ( !isset( $this->discount_array_amount_list[ $coupon_code ] ) ) {

                return $discounts;
            }

            $cart_keys = array_keys( $discounts );

            $totals = $this->discount_array_amount_list[ $coupon_code ];

            $discount_array = array();

            foreach ( $cart_keys as $cart_key ) {

                if ( isset( $totals[ $cart_key ] ) ) {

                    $discount_array[ $cart_key ] = $this->add_number_precision( $totals[ $cart_key ] );
                } else {

                    $discount_array[ $cart_key ] = 0;
                }
            }

            return apply_filters( 'pricely_get-pipeline-coupon-discount-array', $discount_array, $coupon_code );
        }

        public function get_apply_quantity( $quantity, $cart_item, $coupon, $discount_obj ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $quantity;
            }

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return $quantity;
            }

            $item_data = $this->get_item_data( $cart_item );

            if ( !$item_data ) {

                return $quantity;
            }

            return $item_data[ 'quantity' ];
        }

        public function get_individual_use( $individual_use, $coupon ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $individual_use;
            }

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return $individual_use;
            }

            return $discount_data[ 'individual_use' ];
        }

        public function get_product_ids( $product_ids, $coupon ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $product_ids;
            }

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return $product_ids;
            }

            return $discount_data[ 'product_ids' ];
        }

        public function get_is_valid_for_product( $valid, $product, $coupon, $cart_item ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $valid;
            }

            if ( !isset( $cart_item[ 'product_id' ] ) ) {

                return $valid;
            }

            $product_id = $cart_item[ 'product_id' ];

            if ( $cart_item[ 'variation_id' ] > 0 ) {

                $product_id = $cart_item[ 'variation_id' ];
            }

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return $valid;
            }

            $product_ids = $discount_data[ 'product_ids' ];

            return in_array( $product_id, $product_ids );
        }

        public function get_excluded_product_ids( $excluded_product_ids, $coupon ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $excluded_product_ids;
            }

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return $excluded_product_ids;
            }

            return array();
        }

        public function get_product_categories( $product_categories, $coupon ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $product_categories;
            }

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return $product_categories;
            }

            return array();
        }

        public function get_excluded_product_categories( $excluded_product_categories, $coupon ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $excluded_product_categories;
            }

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return $excluded_product_categories;
            }

            return array();
        }

        public function get_exclude_sale_items( $exclude_sale_items, $coupon ) {

            $coupon_code = $this->get_code_from_coupon( $coupon );

            if ( !$coupon_code ) {

                return $exclude_sale_items;
            }

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return $exclude_sale_items;
            }

            return false;
        }

        public function get_coupon_id_from_code( $id, $code, $exclude ) {

            if ( $this->is_internal_call ) {

                return $id;
            }

            $discount_data = $this->get_discount_data( $code, true );

            if ( !$discount_data ) {

                return $id;
            }

            if ( !$discount_data[ 'coupon_id' ] ) {

                return $id;
            }

            return $discount_data[ 'coupon_id' ];
        }

        public function get_coupon_totals( $coupon_code ) {

            $discount_data = $this->get_discount_data( $coupon_code, true );

            if ( !$discount_data ) {

                return false;
            }

            $totals = array(
                'coupon_id' => $discount_data[ 'coupon_id' ],
                'cart_discounts' => $discount_data[ 'cart_discounts' ],
                'total' => $discount_data[ 'total' ],
            );

            return $totals;
        }

        private function maybe_set_external_id( $coupon_code, $coupon_data ) {

            if ( empty( $coupon_code ) ) {

                return;
            }

            $coupon_id = false;

            if ( false !== $coupon_data && isset( $coupon_data[ 'id' ] ) ) {

                $coupon_id = $coupon_data[ 'id' ];
            }

            if ( false !== $coupon_id ) {

                $this->add_external_id( $coupon_code, $coupon_id );
            }

            $this->is_internal_call = true;

            $db_coupon_id = wc_get_coupon_id_by_code( $coupon_code );

            $this->is_internal_call = false;

            if ( $db_coupon_id ) {

                $this->add_external_id( $coupon_code, $db_coupon_id );
            }
        }

        private function add_external_id( $coupon_code, $coupon_id ) {

            if ( isset( $this->internal_coupon_ids[ $coupon_code ] ) ) {

                return;
            }

            $this->external_coupon_ids[ $coupon_code ] = $coupon_id;
        }

        private function add_internal_id( $coupon_code, $coupon_id ) {

            if ( isset( $this->external_coupon_ids[ $coupon_code ] ) ) {

                return;
            }

            $this->internal_coupon_ids[ $coupon_code ] = $coupon_id;
        }

        private function has_external_id( $coupon_code ) {

            if ( isset( $this->external_coupon_ids[ $coupon_code ] ) ) {

                return true;
            }

            return false;
        }

        private function get_code_from_coupon( $coupon ) {

            if ( method_exists( $coupon, 'get_code' ) ) {

                return $coupon->get_code();
            }

            return false;
        }

        private function get_coupon_data( $coupon_code ) {

            $discount_data = $this->get_discount_data( $coupon_code, false );

            if ( !$discount_data ) {

                return false;
            }

            $data = array();

            if ( !$this->has_external_id( $coupon_code ) ) {

                $data[ 'id' ] = $discount_data[ 'coupon_id' ];
            }

            $coupon_amount = apply_filters( 'pricely_get-pipeline-coupon-amount', $discount_data[ 'total' ], $discount_data );

            return array_merge( array(
                'code' => $discount_data[ 'coupon_code' ],
                'amount' => $coupon_amount,
                'status' => 'publish',
                'date_created' => new WC_DateTime(),
                'date_modified' => new WC_DateTime(),
                'date_expires' => $discount_data[ 'date_expires' ],
                'type' => $this->coupon_type,
                'discount_type' => $this->coupon_type,
                'usage_count' => 0,
                'individual_use' => $discount_data[ 'individual_use' ],
                'product_ids' => $discount_data[ 'product_ids' ],
                'excluded_product_ids' => array(),
                'usage_limit' => 0,
                'usage_limit_per_user' => 0,
                'limit_usage_to_x_items' => null,
                'free_shipping' => false,
                'product_categories' => array(),
                'excluded_product_categories' => array(),
                'exclude_sale_items' => false,
                'minimum_amount' => '',
                'maximum_amount' => '',
                'email_restrictions' => array(),
                'used_by' => array(),
                'virtual' => true,
                    ), $data );
        }

        private function get_discount_data( $coupon_code, $use_cache ) {

            if ( isset( $this->coupon_data_list[ $coupon_code ] ) && $use_cache ) {

                return $this->coupon_data_list[ $coupon_code ];
            }

            $this->coupon_data_list[ $coupon_code ] = $this->pipeline_coupons_discounts->get_discount_data( $coupon_code, $this->has_external_id( $coupon_code ), $this->prev_totals );

            return $this->coupon_data_list[ $coupon_code ];
        }

        private function get_item_data( $cart_item ) {

            return $this->pipeline_coupons_discounts->get_item_data( $cart_item );
        }

        private function add_number_precision( $value ) {

            if ( !$value ) {

                return 0.0;
            }

            $cent_precision = pow( 10, Pricely_Totals_Util::get_instance()->get_decimals() );

            return $value * $cent_precision;
        }

        private function set_prev_totals( $discount, $coupon_code, $item_data, $original_total, $is_single, $is_external ) {

            $item_key = $item_data[ 'key' ];

            //set subtotals
            $subtotal = $original_total;

            if ( $is_single ) {

                $qty = $item_data[ 'quantity' ];

                $subtotal = $original_total * $qty;
            }



            if ( !isset( $this->prev_totals[ 'subtotals' ][ $item_key ] ) ) {

                $this->prev_totals[ 'subtotals' ][ $item_key ] = $this->revert_amount( $subtotal, 'pipeline-set-coupon-item-total-' . $coupon_code );
            }


            //set coupon totals
            if ( !isset( $this->prev_totals[ 'coupons' ][ $coupon_code ] ) ) {

                $this->prev_totals[ 'coupons' ][ $coupon_code ] = array();
            }

            $discount_total = $discount;

            if ( $is_single ) {

                $qty = $item_data[ 'quantity' ];

                $discount_total = $discount * $qty;
            }

            $convert_id = 'internal-';

            if ( $is_external ) {

                $convert_id = 'external-';
            }

            $this->prev_totals[ 'coupons' ][ $coupon_code ][ $item_key ] = $this->revert_amount( $discount_total, 'pipeline-set-coupon-item-discount-total-' . $convert_id . $coupon_code );
        }

        private function set_discount_array_amount( $discount, $coupon_code, $item_data ) {

            if ( !isset( $this->discount_array_amount_list[ $coupon_code ] ) ) {

                $this->discount_array_amount_list[ $coupon_code ] = array();
            }

            $cart_key = $item_data[ 'cart_key' ];

            $this->discount_array_amount_list[ $coupon_code ][ $cart_key ] = $discount;
        }
        
        private function convert_amount( $amount, $amount_id ) {

            if ( is_null( $this->currency ) ) {

                $this->currency = Pricely_Currency::get_instance();
            }

            return $this->currency->convert_amount( $amount, $amount_id );
        }

        private function revert_amount( $amount, $amount_id ) {

            if ( is_null( $this->currency ) ) {

                $this->currency = Pricely_Currency::get_instance();
            }

            return $this->currency->revert_amount( $amount, $amount_id );
        }

    }

    Pricely_Pipeline_Coupons_Data::get_instance();
}