<?php
/**
 * File name  sw-invoice-functions.php
 * Utility function file to interact with invoice related data.
 *
 * @author Callistus
 * @package SmartWoo\functions
 */

defined( 'ABSPATH' ) || exit; // Prevent direct access.

/**
 * Dropdown for Invoice Type with filter for custom options.
 *
 * @param array $args Array of arguments.
 *
 * @since 1.0.0
 */
function smartwoo_invoice_type_dropdown( $args = array() ) {
	// Default options
	$options = smartwoo_supported_invoice_types();
		$default_args = array(
		'id'			=> 'invoice_type',
		'class'			=> 'sw-form-input',
		'name'			=> 'invoice_type',
		'selected'		=> '',
		'field_name'	=> 'Invoice Type',
		'required'		=> false
	);

	$parsed_args = wp_parse_args( $args, $default_args );
	?>
	<select 
		class="<?php echo sanitize_html_class( $parsed_args['class'] ); ?>" 
		name="<?php echo sanitize_html_class( $parsed_args['name'] ); ?>"
		id="<?php echo sanitize_html_class( $parsed_args['id'] ); ?>"
		field-name="<?php echo esc_attr( $parsed_args['field_name'] ); ?>"
		<?php echo esc_attr( $parsed_args['required'] ? 'required' : '' ); ?>
	>
		<option value=""><?php esc_html_e( 'Select Invoice Type', 'smart-woo-service-invoicing' ); ?>
		<?php foreach ( $options as $value => $label ) : ?>
			<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $value, $parsed_args['selected'] ); ?>><?php echo esc_html( $label ); ?></option>
		<?php endforeach; ?>
	</select>
	<?php
}

/**
 * Get supported invoice types
 * 
 * @return array $data An associative array of type => title
 */
function smartwoo_supported_invoice_types() {
	return apply_filters( 'smartwoo_supported_invoice_types',
		array(
			'New Service Invoice'       => __( 'New Service Invoice', 'smart-woo-service-invoicing' ),
			'Service Renewal Invoice'   => __( 'Service Renewal Invoice', 'smart-woo-service-invoicing' ),
		)
	);
}

/**
 * Dropdown for Invoice Payment Status with filter for custom options.
 *
 * @param array $args Array of arguments.
 *
 * @since 1.0.0
 */
function smartwoo_invoice_payment_status_dropdown( $args = array() ) {

	$default_args = array(
		'id'			=> 'payment_status',
		'class'			=> 'sw-form-input',
		'name'			=> 'payment_status',
		'selected'		=> '',
		'field_name'	=> 'Payment status',
		'required'		=> false
	);

	$parsed_args = wp_parse_args( $args, $default_args );
	
	$options = apply_filters( 'smartwoo_payment_status',
		array(
			''			=> __( 'Select Payment Status', 'smart-woo-service-invoicing' ),
			'paid'		=> __( 'Paid', 'smart-woo-service-invoicing' ),
			'unpaid'	=> __( 'Unpaid', 'smart-woo-service-invoicing' ),
			'due'		=> __( 'Due', 'smart-woo-service-invoicing' ),
			'cancelled'	=> __( 'Cancel', 'smart-woo-service-invoicing' ),
		)
	);
	?>
	<select 
		class="<?php echo sanitize_html_class( $parsed_args['class'] ); ?>" 
		name="<?php echo sanitize_html_class( $parsed_args['name'] ); ?>"
		id="<?php echo sanitize_html_class( $parsed_args['id'] ); ?>"
		field-name="<?php echo esc_attr( $parsed_args['field_name'] ); ?>"
		<?php echo esc_attr( $parsed_args['required'] ? 'required' : '' ); ?>
	>
		<?php foreach ( $options as $value => $label ) : ?>
			<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $value, $parsed_args['selected'] ); ?>><?php echo esc_html( $label ); ?></option>
		<?php endforeach; ?>
	</select>
	<?php
	
}

/**
 * Checks if an invoice is associated with a service.
 *
 * @param string $invoice_id The ID of the invoice to check.
 *
 * @return bool True if the invoice is associated with a service, false otherwise.
 */
function smartwoo_is_service_invoice( $invoice_id ) {
	$the_invoice = SmartWoo_Invoice_Database::get_invoice_by_id( $invoice_id );
	$service_id = $the_invoice ? $the_invoice->get_service_id() : null;

	return ! is_null( $service_id );
}

/**
 * Checks if a Service has an invoice with a specific invoice_type and transaction_status.
 *
 * @param string $service_id		The ID of Service to check.
 * @param string $invoice_type		The invoice type to check.
 * @param string $payment_status	The invoice payment status.
 *
 * @return string|false The invoice_id if the service has such an invoice, false otherwise.
 *
 * @since 1.0.0
 */
function smartwoo_evaluate_service_invoices( $service_id, $invoice_type, $payment_status ) {

	$args = array(
		'service_id'	=> $service_id,
		'type'			=> $invoice_type,
		'status'		=> $payment_status,
		'limit'			=> 1
		
	);
	$invoices	= SmartWoo_Invoice_Database::get_service_invoices( $args );

	if ( empty( $invoices ) ) {
		return false;
	}

	return $invoices[0]->get_invoice_id();
}


/**
 * Update one or more fields of an existing invoice.
 *
 * @param string $invoice_id The ID of the invoice to update.
 * @param array  $fields     An associative array of fields to update and their new values.
 *
 * @return bool True on success, false on failure.
 */
function smartwoo_update_invoice_fields( $invoice_id, $fields ) {
	$existing_invoice = SmartWoo_Invoice_Database::get_invoice_by_id( $invoice_id );

	if ( ! $existing_invoice ) {
		return false;
	}

	return SmartWoo_Invoice_Database::update_invoice_fields( $invoice_id, $fields );
}

/**
 * Generates a pending WooCommerce order for the payment of an unpaid invoice.
 *
 * @param SmartWoo_Invoice|string        $invoice The ID of the unpaid invoice.
 * @param float|null $total     The total for the order. If not provided (null), invoice total will be used.
 *
 * @return int|false The ID of the newly created order or false on failure.
 *
 * @since 1.1.0
 */
function smartwoo_generate_pending_order( $invoice, $total = null ) {
	$invoice = ( $invoice instanceof SmartWoo_Invoice ) ? $invoice : SmartWoo_Invoice_Database::get_invoice_by_id( $invoice );

	if ( ! $invoice ) {
		return false;
	}

	$order = wc_create_order( array( 'customer_id' => $invoice->get_user_id() ) );

	// Add fees from invoice as a line item.
	if ( $invoice->get_fee() ) {
		$fee_amount = $invoice->get_fee();
		$fee_name   = 'Invoice Fee';
		$fee        = new WC_Order_Item_Fee();
		$fee->set_props(
			array(
				'name'      => $fee_name,
				'tax_class' => '',
				'total'     => $fee_amount,
			)
		);
		$order->add_item( $fee );
	}


	// Use line item with pseudo product name, and use real price to prevents SKU deduction.
	if ( $invoice->get_product() ) {
		$product_name         = $invoice->get_product()->get_name();
		$pseudo_product_name  = $product_name;
		$pseudo_product_price = $invoice->get_amount();
	
		$product = new WC_Order_Item_Product();
		$product->set_props(
			array(
				'name'     => $pseudo_product_name,
				'quantity' => 1,
				'subtotal' => $pseudo_product_price,
				'total'    => $pseudo_product_price,
			)
		);
		$order->add_item( $product );
	}

	// Set the order total based on the provided parameter or use the invoice total.
	$order_total = ( ! is_null( $total ) ) ? $total : $invoice->get_totals();
	$order->set_total( $order_total );
	$order->update_status( 'pending' );

	// Set order signatures.
	$order->set_created_via( SMARTWOO );
	$order->update_meta_data( '_sw_invoice_id', $invoice->get_invoice_id() );
	$order->update_meta_data( '_wc_order_attribution_utm_source', SMARTWOO );
	$order->update_meta_data( '_wc_order_attribution_source_type', 'utm' );

	// Set client billing addresses.
	$customer = $invoice->get_user();
	$order->set_billing_first_name( $customer->get_billing_first_name() );
	$order->set_billing_last_name( $customer->get_billing_last_name() );
	$order->set_billing_company( $customer->get_billing_company() );
	$order->set_billing_address_1( $customer->get_billing_address_1() );
	$order->set_billing_address_2( $customer->get_billing_address_2() );
	$order->set_billing_city( $customer->get_billing_city() );
	$order->set_billing_postcode( $customer->get_billing_postcode() );
	$order->set_billing_country( $customer->get_billing_country() );
	$order->set_billing_state( $customer->get_billing_state() );
	$order->set_billing_email( $customer->get_billing_email() );
	$order->set_billing_phone( $customer->get_billing_phone() );
	// Save order.
	$order->save();

	/**
	 * Fires after we create new invoice orders.
	 * 
	 * @param WC_Order $order
	 */
	do_action( 'smartwoo_new_invoice_order', $order );

	// Return the ID of the newly created order.
	return $order->get_id();
}

/**
 * Generate invoice ID
 *
 * @return string $invoice_id The new Generated Invoice ID.
 * @since 2.2.3 Deprecated the use of uniqid() function for generating invoice ID.
 * @since 2.4.1 Invoice IDs now follow the format: `prefix-yyyyMMdd-xxxxxxxx` where xxxxxxxx is a unique numeric string.
 */
function smartwoo_generate_invoice_id() {
	do {
		$prefix     = smartwoo_get_invoice_id_prefix();
		$timestamp  = SmartWoo_Date_Helper::create_from_timestamp( time() )->format();

		$microtime  = sprintf( '%.6f', microtime( true ) );
		$micro_str  = str_replace( '.', '', $microtime );
		$micro_part = substr( $micro_str, -6 );
		$rand_part  = wp_rand( 1000, 9999 );

		$unique     = $micro_part . $rand_part;
		$invoice_id = $prefix . '-' . $timestamp . '-' . $unique;

	} while ( SmartWoo_Invoice_Database::get_invoice_by_id( $invoice_id ) );

	return apply_filters( 'smartwoo_generate_invoice_id', $invoice_id );
}


/**
 * Get the saved Invoice Number Prefix.
 *
 * @return string The configured Invoice Number Prefix.
 */
function smartwoo_get_invoice_id_prefix() {
	return get_option( 'smartwoo_invoice_id_prefix', 'INV' );
	
}

/**
 * Generates a new invoice and saves it to the database.
 *
 * @param array $args
 *
 * @return SmartWoo_Invoice|false The invoice object, false on failure.
 *
 * @since 1.0.0
 */
function smartwoo_create_invoice( $args = array() ) {
	$default_args	= array(
		'user_id'		=> 0, 
		'product_id'	=> '', 
		'status'		=> '',
		'type'			=> '', 
		'service_id'	=> null,
		'fee' 			=> null,
		'date_due'		=> null,
		'amount'		=> 0,
		'date_paid'		=> null
	);

	$parsed_args	= wp_parse_args( $args, $default_args );	

	$newInvoice = new SmartWoo_Invoice();
	$newInvoice->set_invoice_id( smartwoo_generate_invoice_id() );

	if ( is_numeric( $parsed_args['product_id'] ) && ! empty( is_int( $parsed_args['product_id'] ) ) ) {
		$newInvoice->set_product_id( $parsed_args['product_id'] );
	}

	if ( $parsed_args['amount'] ) {
		$newInvoice->set_amount( $parsed_args['amount'] );
	} else if( $product = wc_get_product( $parsed_args['product_id'] ) ) {
		$newInvoice->set_amount( $product->get_price() );
	}

	$newInvoice->set_status( $parsed_args['status'] );
	$newInvoice->set_user_id( $parsed_args['user_id'] );
	
	$newInvoice->set_billing_address( smartwoo_get_user_billing_address( $parsed_args['user_id'] ) );
	$newInvoice->set_type( $parsed_args['type'] );
	$newInvoice->set_service_id( $parsed_args['service_id'] );
	$newInvoice->set_fee( $parsed_args['fee'] );
	$newInvoice->set_date_due( $parsed_args['date_due'] );

	$newInvoice->set_total( $newInvoice->get_amount() + $newInvoice->get_fee() );
	$newInvoice->set_date_created( current_time( 'mysql' ) );

	if ( 'unpaid' === strtolower( $parsed_args['status'] ) ) {
		$order_id = smartwoo_generate_pending_order( $newInvoice );
		$newInvoice->set_order_id( $order_id );
	}

	return $newInvoice->save() ? $newInvoice : false;
}

/**
 * Get a formated user billing address.
 *
 * @param WC_Customer|int $user_id  The ID of the user
 * @return string Readable address format.
 */
function smartwoo_get_user_billing_address( $user_id ) {
	$customer				= ( $user_id instanceof WC_Customer ) ? $user_id : new WC_Customer( $user_id );
    $billing_address_1		= $customer->get_billing_address_1();
    $billing_address_2		= $customer->get_billing_address_2();
    $billing_city			= $customer->get_billing_city();
    $billing_state			= $customer->get_billing_state();
    $billing_country_code	= $customer->get_billing_country();
    $billing_country_name 	= smartwoo_get_country_name( $billing_country_code );
    $billing_state_name 	= $billing_state;
	$states 				= WC()->countries->get_states( $billing_country_code );

	if ( ! empty( $states ) && isset( $states[$billing_state] ) ) {
		$billing_state_name = $states[$billing_state];
	}

    $address_parts = array_filter(
        array(
            $billing_address_1,
            $billing_address_2,
            $billing_city,
            $billing_state_name,
            $billing_country_name,
        )
    );

    if ( ! empty( $address_parts ) ) {
        $user_billing_address = implode( ', ', $address_parts );
        return $user_billing_address;
    }

    return '';
}

/**
 * Get the full country name from a country code.
 * 
 * @param string $cc Two-letter country code (e.g., 'US', 'GB').
 * @return string The country name if found, otherwise the provided country code.
 *
 * @since 2.3.0
 */
function smartwoo_get_country_name( $cc ) {
    $countries = WC()->countries->get_countries();
    return isset( $countries[ $cc ] ) ? $countries[ $cc ] : $cc;
}

/**
 * Get the full state name from a country and state code.
 * 
 * @param string $cc      Two-letter country code (e.g., 'US', 'GB').
 * @param string $stcode  State code (e.g., 'CA' for California, 'ON' for Ontario).
 * @return string The state name if found, otherwise the provided state code.
 *
 * @since 2.3.0
 */
function smartwoo_get_state_name( $cc, $stcode ) {
    $states = WC()->countries->get_states( $cc );
    return isset( $states[ $stcode ] ) ? $states[ $stcode ] : $stcode;
}

/**
 * Retrieve billing Address from the store and options
 *
 * @return stdClass Object with billing details.
 */
function smartwoo_biller_details() {
	// Retrieve plugin and WooCommerce settings
	$business_name			= get_option( 'smartwoo_business_name', '' );
	$invoice_logo_url		= get_option( 'smartwoo_invoice_logo_url' );
	$admin_phone_numbers	= get_option( 'smartwoo_admin_phone_numbers', '' );
	$store_address			= get_option( 'woocommerce_store_address' );
	$store_city				= get_option( 'woocommerce_store_city' );
	$default_country		= get_option( 'woocommerce_default_country' );

	// Create and populate object with billing details
	$biller_details                     = new stdClass();
	$biller_details->business_name      = $business_name;
	$biller_details->invoice_logo_url   = $invoice_logo_url;
	$biller_details->admin_phone_number	= $admin_phone_numbers;
	$biller_details->store_address      = $store_address;
	$biller_details->store_city         = $store_city;
	$biller_details->default_country    = $default_country;

	// Return the object
	return $biller_details;
}

/**
 * Retrieves and formats the WooCommerce store address.
 *
 * @return string Formatted store address.
 */
function smartwoo_get_formatted_biller_address() {
    $store_address_1	= get_option( 'woocommerce_store_address' );
    $store_address_2	= get_option( 'woocommerce_store_address_2' );
    $store_city			= get_option( 'woocommerce_store_city' );
    $country_base		= wc_get_base_location();
    $store_country_name	= smartwoo_get_country_name( $country_base['country'] );
    $store_state_name	= smartwoo_get_state_name( $country_base['country'], $country_base['state'] );

    $address_parts = array_filter(
        array(
            $store_address_1,
            $store_address_2,
            $store_city,
            $store_state_name,
            $store_country_name,
        )
    );

    if ( ! empty( $address_parts ) ) {
        $store_address = implode( ', ', $address_parts );
        return $store_address;
    }

    // Return an empty string if there's no store address
    return '';
}


/**
 * Get the total amount spent by a user.
 * 
 * @param int $user_id		The user's ID.
 */
function smartwoo_client_total_spent( $user_id ) {
	$customer = new WC_Customer( $user_id );

	return  $customer->get_total_spent();
}

/**
 * Retrieves client's billing email, when billing email is not available
 * the client's login email is used.
 *
 * @param WC_Customer|int $user_id The user's ID 
 * @since 2.0.15
 */
function smartwoo_get_client_billing_email( $user_id ) {
	$user			= ( $user_id instanceof WC_Customer ) ? $user_id : new WC_Customer( $user_id );
	$billing_email	= $user->get_billing_email();

	if ( empty( $billing_email ) ) {
		// Fallback to user's login email address.
		$billing_email = $user->get_email();
	}

	return $billing_email;
}

/**
 * Invoice order Payment URL, specifically for the service invoices.
 * 
 * @param int $order_id WooCommerce order ID
 * @return string The generated order-pay URL
 */
function smartwoo_invoice_pay_url( $order_id ) {
	$order = wc_get_order( $order_id );

	if ( $order && ( $order->get_meta( '_sw_invoice_id' ) || smartwoo_check_if_configured( $order ) ) ) {

		return $order->get_checkout_payment_url() ;
	}
	return "";
}

/**
 * Get invoice preview URL
 *
 * @param int|string $invoice_id Invoice ID
 * @return string|null Escaped URL or null if parameters are empty
 */
function smartwoo_invoice_preview_url( $invoice_id = '' ) {
    $preview_url = '#';

	if ( is_account_page() ) {
        $preview_url = wc_get_endpoint_url( 'smartwoo-invoice', $invoice_id, wc_get_page_permalink( 'myaccount' ) );

    } elseif( ! smartwoo_is_frontend() && is_admin() ) {
		$preview_url = add_query_arg( 
			array( 
				'page' 			=> 'sw-invoices', 
				'tab' 			=> 'view-invoice', 
				'invoice_id'	=> $invoice_id 
			), 
			admin_url( 'admin.php' ) 
		);

	} else {
        $invoice_page_id	= get_option( 'smartwoo_invoice_page_id', 0 );
        $invoice_page_url	= get_permalink( $invoice_page_id );
        $preview_url		= smartwoo_get_endpoint_url( 'view-invoice', $invoice_id, $invoice_page_url );
    }
	return $preview_url;

}

/**
 * Invoice URL.
 */
function smartwoo_invoice_page_url() {
	$invoice_page = absint( get_option( 'smartwoo_invoice_page_id', 0 ) );

	if ( is_account_page () ){
		return wc_get_account_endpoint_url( 'smartwoo-invoice' );
	}

	if ( is_admin() ) {
		return admin_url( 'admin.php?page=sw-invoices' );
	}
	return get_permalink( $invoice_page );
}

 /**
  * Product deletion button.
  */
function smartwoo_delete_invoice_button( $invoice_id ) {
	return '<button title="Delete Invoice" class="delete-invoice-button sw-icon-button-admin" data-invoice-id="' . esc_attr( $invoice_id ) . '"><span class="dashicons dashicons-trash"></span></button>';
}

// Add Ajax actions
add_action( 'wp_ajax_delete_invoice', 'smartwoo_delete_invoice_ajax_callback' );
add_action( 'wp_ajax_nopriv_delete_invoice', 'smartwoo_delete_invoice_ajax_callback' );

function smartwoo_delete_invoice_ajax_callback() {

	if ( ! check_ajax_referer( 'smart_woo_nonce', 'security', false ) ) {
		wp_send_json_error( array( 'message' => 'Action did not pass security check.' ) );
	}

	$invoice_id = isset( $_POST['invoice_id'] ) ? sanitize_text_field( wp_unslash( $_POST['invoice_id'] ) ) : '';

	if ( empty( $invoice_id ) ) {
		wp_send_json_error( array( 'message' => 'Invalid invoice ID.' ) );
	}

	$delete_result = SmartWoo_Invoice_Database::delete_invoice( $invoice_id );

	if ( ! $delete_result ) {
		wp_send_json_error( array( 'message' => 'Unable to delete invoice' ) );
	} else {
		wp_send_json_success( array( 'message' => 'Invoice deleted' ) );
	}
}

/**
 * Marks invoice as paid.
 *
 * @param string|SmartWoo_Invoice $invoice_id   The ID of the invoice to be updated
 * @return bool     false if the invoice is already 'Paid' | true if update is successful
 */
function smartwoo_mark_invoice_as_paid( $invoice_id ) {
	$invoice = ( $invoice_id instanceof SmartWoo_Invoice ) ? $invoice_id : SmartWoo_Invoice_Database::get_invoice_by_id( $invoice_id );

	if ( $invoice && 'paid' !== $invoice->get_status() ) {
		$order = $invoice->get_order();
		if ( ! $order ) {
			return false;
		}

		// Update additional fields in the invoice
		$fields	= array(
			'payment_status'  => 'paid',
			'date_paid'       => current_time( 'mysql' ),
			'transaction_id'  => $order->get_transaction_id(),
			'payment_gateway' => $order->get_payment_method(),
		);
		$updated_invoice = SmartWoo_Invoice_Database::update_invoice_fields( $invoice->get_invoice_id(), $fields );

		if ( false === $updated_invoice ) {
			return false;
		}
		
		/**
		 * Fires after an invoice is marked as paid.
		 * @param SmartWoo_Invoice $updated_invoice The updated invoice object.
		 */
		do_action( 'smartwoo_invoice_is_paid', $updated_invoice );

		return true;

	}
	// Invoice is already paid, terminate further execution.
	return false;
}

/**
 * Counts and renders payment status counts of all invoice for the current user
 */
function smartwoo_all_user_invoices_count() {

	$user_id = get_current_user_id();

	// Get counts for each payment status for the current user.
	$counts = array(
		'paid'      => SmartWoo_Invoice_Database::count_payment_status( $user_id, 'paid' ),
		'unpaid'    => SmartWoo_Invoice_Database::count_payment_status( $user_id, 'unpaid' ),
		'cancelled' => SmartWoo_Invoice_Database::count_payment_status( $user_id, 'cancelled' ),
		'due'       => SmartWoo_Invoice_Database::count_payment_status( $user_id, 'due' ),
	);

	// Generate the HTML.
	$output = '<div class="sw-invoice-status-counts">';
	foreach ( $counts as $status => $count ) {
		$nav_url = smartwoo_get_endpoint_url( 'status', $status );
		$output .= '<div class="sw-user-status-item' . ( ( get_query_var( 'status' ) === $status ) ? ' active' : '' ) . '">';
		$output .= '<p><a href="' . esc_url( $nav_url ) .'">' . ucfirst( $status ) . '</a> <small>' . $count . '</small></p>';
		$output .= '</div>';
	}
	$output .= '</div>';

	return $output;
}

/**
 * Print invoice status with mapped labels.
 * 
 * @param SmartWoo_Invoice $invoice The invoice object.
 * @param array            $classes Additional classes to apply.
 */
function smartwoo_print_invoice_status( SmartWoo_Invoice $invoice, $classes = array() ) {
	$status       = strtolower( $invoice->get_status() );

	// Map invoice statuses to friendly labels.
	$status_labels = array(
		'paid'     => __( 'Paid', 'smart-woo-service-invoicing' ),
		'unpaid'   => __( 'Unpaid', 'smart-woo-service-invoicing' ),
		'cancelled'=> __( 'Cancelled', 'smart-woo-service-invoicing' ),
		'due'      => __( 'Overdue', 'smart-woo-service-invoicing' ),
	);

	$label = isset( $status_labels[ $status ] )
		? $status_labels[ $status ]
		: ucfirst( $status ); // Fallback to raw status.

	// Format status into a valid class name.
	$status_class = str_replace(
		array( ' ', '(', ')' ),
		array( '-', '', '' ),
		$status
	);

	$class_string = implode( ' ', $classes );

	printf(
		'<span class="smartwoo-invoice-status %s %s">%s</span>',
		esc_attr( $class_string ),
		esc_attr( $status_class ),
		esc_html( $label )
	);
}
