<?php

namespace WeDevs\Dokan;

use WC_Order;
use WC_Order_Factory;
use WC_Product;
use WeDevs\Dokan\Commission\OrderCommission;
use WeDevs\Dokan\Commission\OrderLineItemCommission;
use WeDevs\Dokan\Commission\ProductCommission;
use WP_Error;

/**
 * Dokan Commission Class
 *
 * @since 2.9.21
 */
class Commission {

    /**
     * Order id holder
     *
     * @since 2.9.21
     *
     * @var integer
     */
    public $order_id = 0;

    /**
     * Order Line Item Id For Product
     *
     * @since 3.8.0
     *
     * @var int $order_item_id
     */
    protected $order_item_id = 0;

    /**
     * Order quantity holder
     *
     * @since 2.9.21
     *
     * @var integer
     */
    public $quantity = 0;

    /**
     * Calculate gateway fee
     *
     * @deprecated 3.14.0 Use dokan()->fees->calculate_gateway_fee insted.
     *
     * @since 2.9.21
     *
     * @param int $order_id
     *
     * @return void
     */
    public function calculate_gateway_fee( $order_id ) {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->fees->calculate_gateway_fee()' );
        dokan()->fees->calculate_gateway_fee( $order_id );
    }

    /**
     * Set order id
     *
     * @deprecated 3.14.0
     *
     * @since  2.9.21
     *
     * @param int $id
     *
     * @return void
     */
    public function set_order_id( $id ) {
        _deprecated_function( __METHOD__, '3.14.0' );
        $this->order_id = $id;
    }

    /**
     * Set order line item id
     *
     * @deprecated 3.14.0
     *
     * @since 3.8.0
     *
     * @param int $item_id
     *
     * @return void
     */
    public function set_order_item_id( $item_id ) {
        _deprecated_function( __METHOD__, '3.14.0' );
        $this->order_item_id = absint( $item_id );
    }

    /**
     * Get order id
     *
     * @deprecated 3.14.0
     *
     * @since  2.9.21
     *
     * @return int
     */
    public function get_order_id() {
        _deprecated_function( __METHOD__, '3.14.0' );
        return $this->order_id;
    }

    /**
     * Get order line item id
     *
     * @deprecated 3.14.0
     *
     * @since 3.8.0
     *
     * @return int
     */
    public function get_order_item_id() {
        _deprecated_function( __METHOD__, '3.14.0' );
        return $this->order_item_id;
    }

    /**
     * Set order quantity
     *
     * @deprecated 3.14.0
     *
     * @since  2.9.21
     *
     * @param int $number
     *
     * @return void
     */
    public function set_order_qunatity( $number ) {
        _deprecated_function( __METHOD__, '3.14.0' );
        $this->quantity = $number;
    }

    /**
     * Get order quantity
     *
     * @deprecated 3.14.0
     *
     * @since  2.9.21
     *
     * @return int
     */
    public function get_order_qunatity() {
        _deprecated_function( __METHOD__, '3.14.0' );
        return $this->quantity;
    }

    /**
     * Get earning by product
     *
     * @since  2.9.21
     *
     * @param int|WC_Product $product
     * @param string         $context [admin|seller]
     * @param float|null     $price
     *
     * @return float|WP_Error
     */
    public function get_earning_by_product( $product, $context = 'seller', $price = null ) {
        if ( ! $product instanceof WC_Product ) {
            $product = wc_get_product( $product );
        }

        if ( ! $product ) {
            return new WP_Error( 'invalid_product', __( 'Product not found', 'dokan-lite' ), [ 'status' => 400 ] );
        }

        $product_price = is_null( $price ) ? floatval( $product->get_price() ) : floatval( $price );
        $vendor_id     = (int) dokan_get_vendor_by_product( $product, true );
        $product_id    = $product->get_id();

        try {
            $product_commission = dokan_get_container()->get( ProductCommission::class );
            $product_commission->set_product_id( $product_id );
            $product_commission->set_total_amount( $product_price );
            $product_commission->set_vendor_id( $vendor_id );
            $product_commission->set_category_id( 0 );
            $commission = $product_commission->calculate();

            $commission_or_earning = 'admin' === $context ? $commission->get_admin_commission() : $commission->get_vendor_earning();
        } catch ( \Exception $exception ) {
            $commission_or_earning = 0;
        }

        return apply_filters( 'dokan_get_earning_by_product', $commission_or_earning, $product, $context );
    }

    /**
     * Get earning by order
     *
     * @since  2.9.21
     * @since  3.7.19 Shipping tax recipient support added.
     *
     * @param int|WC_Order $order Order.
     * @param string       $context Accepted values are `admin`, `seller`
     *
     * @return float|void|WP_Error|null on failure
     */
    public function get_earning_by_order( $order, $context = 'seller' ) {
        if ( is_numeric( $order ) ) {
            $order = wc_get_order( $order );
        }

        if ( ! $order ) {
            return new WP_Error( __( 'Order not found', 'dokan-lite' ), 404 );
        }

        if ( $order->get_meta( 'has_sub_order' ) ) {
            return;
        }

        // If `_dokan_admin_fee` is found means, the commission has been calculated for this order without the `WeDevs\Dokan\Commission` class.
        // So we'll return the previously earned commission to keep backward compatability.
        $saved_admin_fee = $order->get_meta( '_dokan_admin_fee', true );

        if ( $saved_admin_fee !== '' ) {
            $saved_fee = ( 'seller' === $context ) ? floatval( $order->get_total() ) - floatval( $saved_admin_fee ) : $saved_admin_fee;

            return apply_filters( 'dokan_order_admin_commission', $saved_fee, $order );
        }

        // get earning from order table
        $earning_or_commission = $this->get_earning_from_order_table( $order->get_id(), $context );
        if ( ! is_null( $earning_or_commission ) ) {
            return $earning_or_commission;
        }

        try {
            $order_commission = dokan_get_container()->get( OrderCommission::class );
            $order_commission->set_order( $order );
            $order_commission->calculate();
        } catch ( \Exception $exception ) {
            return new WP_Error( 'commission_calculation_failed', __( 'Commission calculation failed', 'dokan-lite' ), [ 'status' => 500 ] );
        }

        $earning_or_commission = 'admin' === $context ? $order_commission->get_admin_total_earning() : $order_commission->get_vendor_total_earning();

        $earning_or_commission = apply_filters_deprecated( 'dokan_order_admin_commission', [ $earning_or_commission, $order, $context ], '2.9.21', 'dokan_get_earning_by_order' );

        return apply_filters( 'dokan_get_earning_by_order', $earning_or_commission, $order, $context );
    }

    /*
     * Get vendor earning sub-total
     *
     * @param int|WC_Order $order Order.
     *
     * @since 4.1.3
     *
     * @return float|int
     */
    public function get_vendor_earning_subtotal_by_order( $order ) {
        if ( is_numeric( $order ) ) {
            $order = wc_get_order( $order );
        }

        if ( ! $order ) {
            return new WP_Error( 'invalid_order', __( 'Order not found', 'dokan-lite' ), [ 'status' => 404 ] );
        }

        if ( $order->get_meta( 'has_sub_order' ) ) {
            return null;
        }

        try {
            $order_commission = dokan_get_container()->get( OrderCommission::class );
            $order_commission->set_order( $order );
            $order_commission->calculate();
        } catch ( \Exception $exception ) {
            return new WP_Error( 'commission_calculation_failed', __( 'Commission calculation failed', 'dokan-lite' ), [ 'status' => 500 ] );
        }
        $earning = $order_commission->get_vendor_earning_subtotal();

        /**
         * Vendor earning without subsidy for a given order.
         *
         * @param float    $earning Calculated earning (without subsidy).
         * @param WC_Order $order   Order object.
         */
        return apply_filters( 'dokan_get_vendor_earning_subtotal_by_order', $earning, $order );
    }

    /**
     * Validate commission rate
     *
     * @since  2.9.21
     *
     * @param float $rate
     *
     * @return float
     */
    public function validate_rate( $rate ) {
        if ( '' === $rate || ! is_numeric( $rate ) || $rate < 0 ) {
            $rate = 0.0;
        }

        return (float) $rate;
    }

    /**
     * Get vendor wise additional rate
     *
     * @deprecated 3.14.0 Use dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_percentage() insted
     *
     * @since  2.9.21
     *
     * @param int $vendor_id
     *
     * @return float|null on failure
     */
    public function get_vendor_wise_rate( $vendor_id ) {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_percentage()' );
        return dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_percentage();
    }

    /**
     * Get vendor wise additional fee
     *
     * @deprecated 3.14.0 Use dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_flat() instead
     *
     * @since  2.9.21
     *
     * @param int $vendor_id
     *
     * @return float|null on failure
     */
    public function get_vendor_wise_additional_fee( $vendor_id ) {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_flat()' );
        return dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_flat();
    }

    /**
     * Get vendor wise additional type
     *
     * @deprecated 3.14.0 Use dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_type() instead
     *
     * @since  2.9.21
     *
     * @param int $vendor_id
     *
     * @return float|null on failure
     */
    public function get_vendor_wise_type( $vendor_id ) {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_type()' );
        return dokan()->vendor->get( $vendor_id )->get_commission_settings()->get_type();
    }

    /**
     * Get earning from order table
     *
     * @since  2.9.21
     *
     * @param int    $order_id
     * @param string $context
     * @param bool   $raw
     *
     * @return float|array|null on failure
     */
    public function get_earning_from_order_table( $order_id, $context = 'seller', $raw = false ) {
        global $wpdb;

        $cache_key     = "get_earning_from_order_table_{$order_id}_{$context}";
        $cache_key_raw = $cache_key . '_raw';

        $earning = Cache::get( $raw ? $cache_key_raw : $cache_key );
        if ( false !== $earning ) {
            return $earning;
        }

        $result = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT `net_amount`, `order_total` FROM {$wpdb->dokan_orders} WHERE `order_id` = %d",
                $order_id
            )
        );

        if ( ! $result ) {
            return null;
        }

        $raw_earning = [
            'net_amount'  => (float) $result->net_amount,
            'order_total' => (float) $result->order_total,
        ];

        $earning = ( 'seller' === $context )
            ? $raw_earning['net_amount']
            : $raw_earning['order_total'] - $raw_earning['net_amount'];

        Cache::set( $cache_key, $earning );
        Cache::set( $cache_key_raw, $raw_earning );

        /**
         * Hooks to modify the earning from order table.
         *
         * @since 4.0.2
         *
         * @param float|array $earning  The earning amount or raw data.
         * @param int         $order_id The order ID.
         * @param string      $context  The context (admin or seller).
         * @param bool        $raw      Whether to return raw data or not.
         */
        return apply_filters(
            'dokan_get_earning_from_order_table',
            $raw ? $raw_earning : $earning,
            $order_id,
            $context,
            $raw
        );
    }

    /**
     * Get shipping fee recipient
     *
     * @deprecated 3.14.0 Use dokan()->fees->get_shipping_fee_recipient() instead
     *
     * @since  2.9.21
     * @since  3.4.1 introduced the shipping fee recipient hook
     *
     * @param WC_Order|int $order
     *
     * @return string
     */
    public function get_shipping_fee_recipient( $order ) {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->fees->get_shipping_fee_recipient()' );

        return dokan()->fees->get_shipping_fee_recipient( $order );
    }

    /**
     * Get tax fee recipient
     *
     * @deprecated 3.14.0 Use dokan()->fees->get_tax_fee_recipient() instead
     *
     * @since  2.9.21
     * @since  3.4.1 introduced the tax fee recipient hook
     *
     * @param WC_Order|int $order
     *
     * @return string|WP_Error
     */
    public function get_tax_fee_recipient( $order ) {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->fees->get_tax_fee_recipient()' );

        return dokan()->fees->get_tax_fee_recipient( $order );
    }

    /**
     * Get shipping tax fee recipient.
     *
     * @deprecated 3.14.0 Use dokan()->fees->get_shipping_tax_fee_recipient() instead
     *
     * @since 3.7.19
     *
     * @param WC_Order $order Order.
     *
     * @return string
     */
    public function get_shipping_tax_fee_recipient( $order ): string {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->fees->get_shipping_tax_fee_recipient()' );
        return dokan()->fees->get_shipping_tax_fee_recipient( $order );
    }

    /**
     * Get total shipping tax refunded for the order.
     *
     * @deprecated 3.14.0 Use dokan()->fees->get_total_shipping_tax_refunded() instead
     *
     * @since 3.7.19
     *
     * @param WC_Order $order Order.
     *
     * @return float
     */
    public function get_total_shipping_tax_refunded( WC_Order $order ): float {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->fees->get_total_shipping_tax_refunded()' );

        return dokan()->fees->get_total_shipping_tax_refunded( $order );
    }

    /**
     * Get processing fee
     *
     * @deprecated 3.14.0 Use dokan()->fees->get_processing_fee instead.
     *
     * @since 3.0.4
     *
     * @param WC_Order $order
     *
     * @return float
     */
    public function get_processing_fee( $order ) {
        _deprecated_function( __METHOD__, '3.14.0', 'dokan()->fees->get_processing_fee' );

        return dokan()->fees->get_processing_fee( $order );
    }

    /**
     * Get all the orders to be processed
     *
     * @since 3.0.4
     *
     * @param WC_Order $order
     *
     * @return WC_Order[]
     */
    public function get_all_order_to_be_processed( $order ) {
        $all_orders = [];

        if ( $order->get_meta( 'has_sub_order' ) ) {
            $all_orders = dokan()->order->get_child_orders( $order->get_id() );
        } else {
            $all_orders[] = $order;
        }

        return $all_orders;
    }

    /**
     * Calculate commission (commission priority [1.product, 2.category, 3.vendor, 4.global] wise)
     * I this function the calculation was written for vendor perspective it is deprecated now it is recomanded to use `get_commission` method it works fo admin perspective.
     *
     * @deprecated 3.14.0 Use get_commission() instead.
     *
     * @since  2.9.21
     *
     * @param int   $product_id
     * @param float $product_price
     * @param int   $vendor_id
     *
     * @return float
     */
    public function calculate_commission( $product_id, $product_price, $vendor_id = null ) {
        _deprecated_function( __METHOD__, '3.14.0', 'get_commission' );

        $commission_data = $this->get_commission(
            [
                'order_item_id'  => $this->get_order_item_id(),
                'total_amount'   => $product_price,
                'total_quantity' => $this->get_order_qunatity(),
                'product_id'     => $product_id,
                'vendor_id'      => $vendor_id,
            ],
            true
        );

        $settings       = $commission_data->get_settings();
        $percentage       = $settings->get_percentage();
        $flat             = $settings->get_flat();
        $commission_type = $settings->get_type();

        return apply_filters(
            'dokan_after_commission_calculation',
            $commission_data->get_vendor_earning() ?? 0,
            $percentage,
            $commission_type,
            $flat,
            $product_price,
            $this->get_order_id()
        );
    }

    /**
     * Returns all the commission types that ware in dokan. These types were existed before dokan lite version 3.14.0
     *
     * @since 3.14.0
     *
     * @return array
     */
    public function get_legacy_commission_types() {
        return [
            'combine'    => __( 'Combine', 'dokan-lite' ),
            'percentage' => __( 'Percentage', 'dokan-lite' ),
            'flat'       => __( 'Flat', 'dokan-lite' ),
        ];
    }

    /**
     * Returns commission (commission priority [1.Order item if exists. 2.product, 3.vendor, 4.global] wise)
     *
     * @since 3.14.0
     *
     * @param array $args {
     *     Accepted arguments are below.
     *
     *     @type int       $order_item_id  Order item id. Default ''. Accepted values numbers.
     *     @type float|int $total_amount   The amount on which the commission will be calculated. Default 0. Accepted values numbers.
     *                                     Ff you want to calculate for order line item the $total_amount should be total line item amount and
     *                                     $total_quantity should be total line item quantity. EX: for product item apple with cost $100 then $total_amount = 500, $total_quantity = 5
     *                                     or if you want to calculate for product price the $total_amount should be the product price and $total_quantity should be 1
     *                                     EX: for product apple with cost $100 then $total_amount = 100, $total_quantity = 1
     *     @type int       $total_quantity This is the total quantity that represents the $total_amounts item units. Default 1. Accepted values numbers.
     *                                     Please read $total_amount doc above to understand clearly.
     *     @type int       $product_id     Product id. Default 0. Accepted values numbers.
     *     @type int       $vendor_id      Vendor id. Default ''. Accepted values numbers.
     *     @type int       $category_id    Product category id. Default 0'. Accepted values numbers.
     * }
     * @param boolean $auto_save                              If true, it will save the calculated commission automatically to the given `$order_item_id`. Default 'false`. Accepted values boolean.
     * @param boolean $override_total_amount_by_product_price If true, it will override the `$total_amount` by the product price if the `$total_amount` is empty and `$order_item_id` is empty. Default 'true`. Accepted values boolean.
     *
     * @return \WeDevs\Dokan\Commission\Model\Commission
     */
    public function get_commission( $args = [], $auto_save = false, $override_total_amount_by_product_price = true ) {
        $order_item_id    = ! empty( $args['order_item_id'] ) ? $args['order_item_id'] : '';
        $total_amount     = ! empty( $args['total_amount'] ) ? $args['total_amount'] : 0;
        $product_id       = ! empty( $args['product_id'] ) ? $args['product_id'] : 0;
        $vendor_id        = ! empty( $args['vendor_id'] ) ? $args['vendor_id'] : '';
        $category_id      = ! empty( $args['category_id'] ) ? $args['category_id'] : 0;

        if ( $order_item_id ) {
            $order_item = WC_Order_Factory::get_order_item( $order_item_id );

            try {
                $line_item_commission = dokan_get_container()->get( OrderLineItemCommission::class );
                $line_item_commission->set_order( $order_item->get_order() );
                $line_item_commission->set_item( $order_item );

                return $line_item_commission->calculate();
            } catch ( \Exception $e ) {
                dokan_log( $e->getMessage() );
            }
        } else {
            try {
                $product_commission = dokan_get_container()->get( ProductCommission::class );
                $product_commission->set_product_id( $product_id );
                $product_commission->set_category_id( $category_id );
                $product_commission->set_total_amount( $total_amount );
                $product_commission->set_vendor_id( $vendor_id );
                return $product_commission->calculate();
            } catch ( \Exception $e ) {
                dokan_log( $e->getMessage() );
            }
        }
    }
}
