<?php

namespace WeDevs\DokanPro\Modules\Paystack\Refund;

use Exception;
use WC_Order;
use WeDevs\Dokan\Exceptions\DokanException;
use WeDevs\Dokan\Models\VendorBalance;
use WeDevs\DokanPro\Modules\Paystack\Api\Transaction;
use WeDevs\DokanPro\Modules\Paystack\Support\Helper;
use WeDevs\DokanPro\Refund\Refund;
use WeDevs\DokanPro\Modules\Paystack\Api\Refund as RefundApi;

class RefundHandler {
    public function __construct() {
        add_action( 'dokan_refund_approve_before_insert', [ $this, 'process_refund' ], 10, 3 );
        add_filter( 'dokan_pro_exclude_auto_approve_api_refund_request', [ $this, 'exclude_auto_approve_api_refund' ] );
        add_filter( 'dokan_refund_approve_vendor_refund_amount', [ $this, 'get_vendor_refund_amount' ], 10, 3 );
    }

	/**
	 * This method will refund payments to seller.
	 *
     * @param Refund $refund
     * @param array  $args
     * @param float  $vendor_payout_refund The vendor refund amount after the gateway fee is deducted.
     *
	 * @since 4.1.1
	 * @since 5.0.8 Added $args and $vendor_payout_refund params; inserts a debit
	 *                        reversal entry into the vendor balance table on successful refund.
	 *
	 * @return void
	 * @throws Exception
	 */
    public function process_refund( $refund, $args = [], $vendor_payout_refund = 0 ) {
        // get code editor suggestion on refund object
        if ( ! $refund instanceof Refund ) {
            return;
        }

        $order = wc_get_order( $refund->get_order_id() );

        // return if $order is not an instance of WC_Order
        if ( ! $order instanceof WC_Order ) {
            return;
        }

        // return if not paid with dokan paystack payment gateway
        if ( Helper::get_gateway_id() !== $order->get_payment_method() ) {
            return;
        }

        // Get parent order id, because charge id is stored on parent order id
        $parent_order_id = $order->get_parent_id() ? $order->get_parent_id() : $order->get_id();
        $parent_order = wc_get_order( $parent_order_id );
        if ( ! $parent_order ) {
            return;
        }

        // Step 1: check if reference id exists
        $reference = $parent_order->get_meta( '_dokan_paystack_reference', true );

        if ( empty( $reference ) ) {
            // we can't automatically reverse vendor balance, so manual refund and approval is required
            $order->add_order_note( __( 'Dokan Paystack Refund Error: Automatic refund is not possible for this order.', 'dokan' ) );
            return;
        }

        try {
            // step 2: process customer refund on the paystack
			$paystack_refund = $this->refund( $refund, $order, $reference );
			$refund_message = sprintf(
            /* translators: 1: Refund amount 2: Refund transaction id 3: Refund message */
                __( 'Refunded from admin paystack account: %1$s. Refund ID: %2$s. Reason - %3$s', 'dokan' ),
                wc_price( (float) $refund->get_refund_amount(), [ 'currency' => $order->get_currency() ] ),
                $paystack_refund['data']['id'],
                $refund->get_refund_reason()
			);
			$order->add_order_note( $refund_message );
			// save order meta
			$order->save();

            // step 3: reverse the vendor's share of the refund from the vendor balance
            $this->insert_refund_reversal_balance( $refund, $order, (float) $vendor_payout_refund );
        } catch ( Exception $e ) {
            $error_message = sprintf(
                /* translators: 1: refund id 2: order id 3: error message */
                __( 'Dokan Paystack Refund Error: Refund failed on Paystack End. Manual Refund Required. Refund ID: %1$d, Order ID: %2$d, Error Message: %3$s', 'dokan' ),
                $refund->get_id(),
                $refund->get_order_id(),
                $e->getMessage()
            );
            dokan_log( $error_message, 'error' );
            $order->add_order_note( $error_message );
        }
    }

    /**
     * Refunds the payment using Paystack API.
     *
     * @param Refund $refund
     * @param WC_Order $order
     * @param string $reference
     *
     * @return array
     * @throws DokanException
     */
    protected function refund( $refund, $order, $reference ): array {
        $amount = (float) $refund->get_refund_amount();
        // Check if the refund amount is valid
        if ( $amount <= 0 ) {
            throw new DokanException(
                'dokan_paystack_invalid_refund_amount',
                esc_html__( 'Invalid refund amount.', 'dokan' )
            );
        }
        // Verify the transaction
        $transaction = Transaction::verify( $reference );

        // check the refund amount is less than or equal to the transaction amount
        if ( $transaction['data']['amount'] < Helper::format_paystack_amount( $amount ) ) {
            throw new DokanException(
                'dokan_paystack_refund_amount_exceeds_transaction',
                esc_html__( 'Refund amount exceeds the transaction amount.', 'dokan' )
            );
        }

        do_action( 'dokan_paystack_refund_before_create', $order, $refund );

        // Prepare refund arguments
        $args = apply_filters(
            'dokan_paystack_refund_args', [
				'transaction'   => $reference,
				'amount'        => Helper::format_paystack_amount( $amount ),
			], $refund, $order
        );
        // Call the Paystack API to process the refund
        $response = RefundApi::create( $args );

        do_action( 'dokan_paystack_refund_after_created', $order, $response );

        return $response;
    }

    /**
     * Insert a debit entry into the vendor balance table to reverse the vendor's
     * share of the refund.
     *
     * The refund approval flow inserts a credit entry for the vendor's share of
     * the refund, but the vendor was already paid via Paystack split payment. This
     * debit entry offsets that credit so the vendor balance reflects the reversal.
     * The `wc-completed` status makes the debit count towards the balance.
     *
     * @since 5.0.8
     *
     * @param Refund   $refund The Dokan refund request.
     * @param WC_Order $order  The order being refunded.
     * @param float    $amount The vendor payout amount to reverse.
     *
     * @return void
     */
    protected function insert_refund_reversal_balance( Refund $refund, WC_Order $order, float $amount ) {
        if ( $amount <= 0 ) {
            return;
        }

        $balance_date = current_time( 'mysql' );

        $vendor_balance = dokan()->get_container()->get( VendorBalance::class );
        $vendor_balance->set_vendor_id( $refund->get_seller_id() );
        $vendor_balance->set_trn_id( $order->get_id() );
        $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_REFUND );
        $vendor_balance->set_particulars( 'Refund Reversed Via ' . Helper::get_gateway_title() );
        $vendor_balance->set_debit( $amount );
        $vendor_balance->set_trn_date( $balance_date );
        $vendor_balance->set_balance_date( $balance_date );
        $vendor_balance->set_status( 'wc-completed' ); // @see \WeDevs\Dokan\Vendor\Vendor::get_balance()
        $vendor_balance->save();
    }

    /**
     * Deduct the vendor's prorated gateway fee share from the payout refund amount.
     *
     * When the vendor pays the gateway fee, the vendor received the earning
     * minus the fee via split payment. The vendor balance credit entry keeps
     * the full earning share, while the debit reversal only recovers what the
     * vendor was actually paid — so the prorated fee share is deducted here.
     *
     * @since 5.0.8
     *
     * @param float  $vendor_refund The vendor payout refund amount.
     * @param array  $args
     * @param Refund $refund        The Dokan refund request.
     *
     * @return float
     */
    public function get_vendor_refund_amount( $vendor_refund, $args, $refund ) {
        $order = wc_get_order( $refund->get_order_id() );

        if ( ! $order instanceof WC_Order || Helper::get_gateway_id() !== $order->get_payment_method() ) {
            return $vendor_refund;
        }

        if ( 'seller' !== $order->get_meta( 'dokan_gateway_fee_paid_by' ) ) {
            return $vendor_refund;
        }

        $gateway_fee = (float) $order->get_meta( 'dokan_gateway_fee' );
        $order_total = (float) $order->get_total();

        if ( $gateway_fee <= 0 || $order_total <= 0 ) {
            return $vendor_refund;
        }

        $prorated_fee = $gateway_fee * ( (float) $refund->get_refund_amount() / $order_total );

        return max( (float) $vendor_refund - $prorated_fee, 0 );
    }

    /**
     * Exclude dokan paystack from auto approve api refund.
     *
     * @since 4.1.1
     *
     * @param array $payment_gateways
     *
     * @return array
     */
    public function exclude_auto_approve_api_refund( array $payment_gateways ): array {
        $payment_gateways[] = Helper::get_gateway_id();
        return $payment_gateways;
    }
}
