<?php
/**
 * Checkout orchestration.
 *
 * @package WPDudeProducts
 */

declare(strict_types=1);

namespace WPDude\Products\Checkout;

use WPDude\Products\Logging\Logger;
use WPDude\Products\Orders\Order_Repository;
use WPDude\Products\Orders\Statuses;
use WPDude\Products\Products\Post_Type as Product_Post_Type;
use WPDude\Products\Products\Product_Meta;
use WPDude\Products\Products\Stock_Service;
use WPDude\Products\Providers\Stripe\Stripe_Provider;
use WPDude\Products\Settings\Settings;
use WPDude\Products\Frontend\Product_View;

/**
 * Creates pending orders and Stripe Checkout Sessions.
 */
final class Checkout_Service {

	private Order_Repository $orders;
	private Stripe_Provider $stripe;

	public function __construct(
		?Order_Repository $orders = null,
		?Stripe_Provider $stripe = null
	) {
		$this->orders = $orders ?? new Order_Repository();
		$this->stripe = $stripe ?? new Stripe_Provider();
	}

	/**
	 * Start checkout for a product.
	 *
	 * @param array<string, string> $selected_options Option name => value (options products).
	 * @return array{url: string, order_uuid: string}|\WP_Error
	 */
	public function start( int $product_id, int $qty = 1, array $selected_options = array(), string $return_url = '' ) {
		$product = get_post( $product_id );
		if ( ! $product || Product_Post_Type::POST_TYPE !== $product->post_type || 'publish' !== $product->post_status ) {
			return new \WP_Error( 'invalid_product', __( 'Product not found.', 'dude-commerce' ), array( 'status' => 404 ) );
		}

		if ( '' === Settings::get_secret_key() ) {
			return new \WP_Error( 'stripe_not_configured', __( 'Stripe is not configured.', 'dude-commerce' ), array( 'status' => 500 ) );
		}

		if ( Product_Meta::is_sold_individually( $product_id ) ) {
			$qty = 1;
		}
		$qty = max( 1, $qty );

		$type             = Product_Meta::get_type( $product_id );
		$combination_key  = '';
		$combination_label = '';

		if ( Product_Meta::TYPE_OPTIONS === $type ) {
			$resolved = $this->resolve_combination( $product_id, $selected_options );
			if ( is_wp_error( $resolved ) ) {
				return $resolved;
			}
			$combination_key   = (string) $resolved['key'];
			$combination_label = (string) $resolved['label'];
		}

		$unit = Stock_Service::unit_price_minor( $product_id, $combination_key );
		if ( $unit <= 0 ) {
			return new \WP_Error( 'invalid_price', __( 'This product cannot be purchased (invalid price).', 'dude-commerce' ), array( 'status' => 400 ) );
		}

		$available = Stock_Service::available( $product_id, $combination_key );
		if ( $available < $qty ) {
			return new \WP_Error( 'insufficient_stock', __( 'Not enough stock for this purchase.', 'dude-commerce' ), array( 'status' => 409 ) );
		}

		$currency        = Product_Meta::get_currency( $product_id );
		$shipping_type   = Product_Meta::get_shipping_type( $product_id );
		$shipping_amount = Product_Meta::SHIPPING_FLAT === $shipping_type
			? Product_Meta::get_shipping_amount_minor( $product_id )
			: 0;

		$name = trim( (string) $product->post_title );
		if ( '' === $name ) {
			$name = __( 'Product', 'dude-commerce' );
		}
		if ( '' !== $combination_label ) {
			$name .= ' — ' . $combination_label;
		}

		$line_amount   = $unit * $qty;
		$uuid          = wp_generate_uuid4();
		$return_token  = Order_Repository::generate_return_token();
		$image_id  = Stock_Service::line_image_id( $product_id, $combination_key );
		$site_url  = Stock_Service::checkout_image_url( $product_id, $combination_key );
		$line_sku  = Stock_Service::line_sku( $product_id, $combination_key );
		$image_url = '';
		if ( $image_id > 0 ) {
			// On Local, Stripe cannot fetch *.local URLs — upload a Stripe FileLink.
			$image_url = $this->stripe->ensure_public_image_url( $image_id, $site_url );
		} elseif ( '' !== $site_url ) {
			$image_url = $site_url;
		}

		$line_item = array(
			'name'            => $name,
			'unit_amount'     => $unit,
			'qty'             => $qty,
			'combination_key' => $combination_key,
			'sku'             => $line_sku,
			'image_id'        => $image_id,
			'image_url'       => $image_url,
		);

		$order_id = $this->orders->create(
			array(
				'uuid'            => $uuid,
				'status'          => Statuses::PENDING,
				'product_id'      => $product_id,
				'combination_key' => $combination_key,
				'qty'             => $qty,
				'amount'          => $line_amount,
				'shipping_amount' => $shipping_amount,
				'tax_amount'      => 0,
				'currency'        => $currency,
				'line_item'       => $line_item,
				'return_token'    => $return_token,
			)
		);

		if ( $order_id <= 0 ) {
			return new \WP_Error( 'order_create_failed', __( 'Could not create order.', 'dude-commerce' ), array( 'status' => 500 ) );
		}

		$success_url = $this->build_checkout_return_url( $return_url, 'success', $uuid, $return_token, $product_id );
		$cancel_url  = $this->build_checkout_return_url( $return_url, 'cancel', $uuid, $return_token, $product_id );

		$product_data = array( 'name' => $name );
		if ( '' !== $image_url ) {
			$product_data['images'] = array( $image_url );
		}

		Logger::instance()->info(
			'Checkout product image',
			array(
				'product_id'      => $product_id,
				'combination_key' => $combination_key,
				'image_id'        => $image_id,
				'has_image_url'   => '' !== $image_url,
			)
		);

		$stripe_tax = Settings::is_stripe_tax_enabled();
		if ( $stripe_tax ) {
			$product_data['tax_code'] = Settings::get_stripe_tax_code();
		}

		$price_data = array(
			'currency'     => $currency,
			'unit_amount'  => $unit,
			'product_data' => $product_data,
		);
		if ( $stripe_tax ) {
			$price_data['tax_behavior'] = Settings::get_stripe_tax_behavior();
		}

		$countries = Settings::get_sell_to_countries();

		// One delivery address only: collect shipping, leave billing on auto.
		// Stripe Tax uses the shipping address when present; do not set billing to
		// "required" (that shows a separate billing address / “different address” UI).
		$session_args = array(
			'mode'                        => 'payment',
			'success_url'                 => $success_url,
			'cancel_url'                  => $cancel_url,
			'client_reference_id'         => $uuid,
			'customer_creation'           => 'always',
			'billing_address_collection'  => 'auto',
			'shipping_address_collection' => array(
				'allowed_countries' => $countries,
			),
			'line_items'                  => array(
				array(
					'quantity'   => $qty,
					'price_data' => $price_data,
				),
			),
			'metadata'                    => array(
				'wpdude_dc_product_id'      => (string) $product_id,
				'wpdude_dc_order_uuid'      => $uuid,
				'wpdude_dc_combination_key' => $combination_key,
				'wpdude_dc_qty'             => (string) $qty,
				'wpdude_dc_site_url'        => home_url( '/' ),
				'wpdude_dc_plugin_version'  => WPDUDE_DC_VERSION,
			),
			'idempotency_key'             => 'wpdude_dc_checkout_' . $uuid,
		);

		if ( Product_Meta::SHIPPING_FLAT === $shipping_type && $shipping_amount > 0 ) {
			$session_args['shipping_options'] = array(
				array(
					'shipping_rate_data' => array(
						'type'         => 'fixed_amount',
						'fixed_amount' => array(
							'amount'   => $shipping_amount,
							'currency' => $currency,
						),
						'display_name' => __( 'Flat rate shipping', 'dude-commerce' ),
					),
				),
			);
		} else {
			$session_args['shipping_options'] = array(
				array(
					'shipping_rate_data' => array(
						'type'         => 'fixed_amount',
						'fixed_amount' => array(
							'amount'   => 0,
							'currency' => $currency,
						),
						'display_name' => __( 'Free shipping', 'dude-commerce' ),
					),
				),
			);
		}

		if ( $stripe_tax ) {
			$session_args['automatic_tax'] = array( 'enabled' => true );
		}

		/**
		 * Filter Stripe Checkout Session arguments.
		 *
		 * @param array<string, mixed> $session_args Args.
		 * @param int                  $product_id   Product ID.
		 * @param int                  $order_id     Order ID.
		 */
		$session_args = apply_filters( 'wpdude_dc_checkout_session_args', $session_args, $product_id, $order_id );

		$session = $this->stripe->create_checkout_session( $session_args );
		if ( is_wp_error( $session ) ) {
			$this->orders->update( $order_id, array( 'status' => Statuses::FAILED ) );
			return $session;
		}

		$session_id = (string) ( $session['id'] ?? '' );
		$url        = (string) ( $session['url'] ?? '' );

		$this->orders->update(
			$order_id,
			array(
				'stripe_checkout_session_id' => $session_id,
			)
		);

		Logger::instance()->info(
			'Checkout session created',
			array(
				'order_uuid' => $uuid,
				'product_id' => $product_id,
			)
		);

		if ( '' === $url ) {
			return new \WP_Error( 'stripe_error', __( 'We could not start the checkout. Please try again.', 'dude-commerce' ) );
		}

		return array(
			'url'        => $url,
			'order_uuid' => $uuid,
		);
	}

	/**
	 * Resolve combination from selected option values.
	 *
	 * @param array<string, string> $selected Selected name => value.
	 * @return array{key: string, label: string}|\WP_Error
	 */
	private function resolve_combination( int $product_id, array $selected ) {
		$clean = array();
		foreach ( $selected as $name => $value ) {
			$name  = sanitize_text_field( (string) $name );
			$value = sanitize_text_field( (string) $value );
			if ( '' !== $name && '' !== $value ) {
				$clean[ $name ] = $value;
			}
		}

		$key = Product_Meta::combination_key( $clean );
		foreach ( Product_Meta::get_combinations( $product_id ) as $combo ) {
			if ( (string) ( $combo['key'] ?? '' ) === $key ) {
				$parts = array();
				foreach ( (array) ( $combo['options'] ?? array() ) as $n => $v ) {
					$parts[] = $n . ': ' . $v;
				}
				return array(
					'key'   => $key,
					'label' => implode( ' / ', $parts ),
				);
			}
		}

		return new \WP_Error( 'invalid_combination', __( 'Please select a valid combination.', 'dude-commerce' ), array( 'status' => 400 ) );
	}

	/**
	 * Build a same-site Stripe return URL (scrolls to the product section via hash).
	 */
	private function build_checkout_return_url( string $return_url, string $status, string $uuid, string $return_token, int $product_id = 0 ): string {
		$fallback = home_url( '/' );
		$base     = $this->sanitize_same_site_url( $return_url );
		if ( '' === $base ) {
			$base = $fallback;
		}

		// Drop any existing fragment before adding query args (add_query_arg strips it anyway).
		$fragment = '';
		$hash_pos = strpos( $base, '#' );
		if ( false !== $hash_pos ) {
			$base = substr( $base, 0, $hash_pos );
		}
		if ( $product_id > 0 ) {
			$fragment = Product_View::section_anchor( $product_id );
		}

		$base = remove_query_arg( array( 'wpdude_dc_checkout', 'transaction', 'order', 'error', 'wpdude_dc_rt' ), $base );

		$url = add_query_arg(
			array(
				'wpdude_dc_checkout' => $status,
				'transaction'        => $uuid,
				'wpdude_dc_rt'       => $return_token,
			),
			$base
		);

		if ( '' !== $fragment ) {
			$url .= '#' . $fragment;
		}

		return $url;
	}

	/**
	 * Accept only http(s) URLs on this site (same literal host, scheme, and port).
	 */
	private function sanitize_same_site_url( string $url ): string {
		$url = esc_url_raw( trim( $url ) );
		if ( '' === $url || ! preg_match( '#^https?://#i', $url ) ) {
			return '';
		}

		$home  = wp_parse_url( home_url() );
		$parts = wp_parse_url( $url );
		if ( ! is_array( $home ) || ! is_array( $parts ) ) {
			return '';
		}

		$home_host = isset( $home['host'] ) ? strtolower( (string) $home['host'] ) : '';
		$url_host  = isset( $parts['host'] ) ? strtolower( (string) $parts['host'] ) : '';
		if ( '' === $home_host || '' === $url_host || $home_host !== $url_host ) {
			return '';
		}

		$home_scheme = strtolower( (string) ( $home['scheme'] ?? 'https' ) );
		$url_scheme  = strtolower( (string) ( $parts['scheme'] ?? '' ) );
		if ( ! in_array( $url_scheme, array( 'http', 'https' ), true ) || $home_scheme !== $url_scheme ) {
			return '';
		}

		if ( $this->url_port( $home_scheme, $home['port'] ?? null ) !== $this->url_port( $url_scheme, $parts['port'] ?? null ) ) {
			return '';
		}

		return $url;
	}

	/**
	 * Explicit or scheme-default port.
	 *
	 * @param int|string|null $port Parsed port.
	 */
	private function url_port( string $scheme, $port ): int {
		if ( is_int( $port ) || ( is_numeric( $port ) && (string) (int) $port === (string) $port ) ) {
			return (int) $port;
		}

		return 'https' === $scheme ? 443 : 80;
	}
}
