<?php
/** Shared WooCommerce order helpers, privacy gates, and schemas. */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

define( 'WPAGENT_COMPANION_OPT_WC_ORDER_GRANTS', 'wp_agent_companion_wc_order_capability_grants' );

/** @return string[] */
function wpagent_wc_order_required_capabilities(): array {
	return array( 'read_private_shop_orders', 'edit_shop_orders' );
}

/** @return array<int,string[]> */
function wpagent_wc_order_capability_grants(): array {
	$stored = get_option( WPAGENT_COMPANION_OPT_WC_ORDER_GRANTS, array() );
	$grants = array();
	foreach ( is_array( $stored ) ? $stored : array() as $user_id => $capabilities ) {
		$user_id = absint( $user_id );
		if ( ! $user_id || ! is_array( $capabilities ) ) {
			continue;
		}
		$allowed = array_values( array_intersect( wpagent_wc_order_required_capabilities(), array_map( 'sanitize_key', $capabilities ) ) );
		if ( $allowed ) {
			$grants[ $user_id ] = $allowed;
		}
	}
	return array_slice( $grants, 0, 200, true );
}

/** @param array<int,string[]> $grants */
function wpagent_wc_store_order_capability_grants( array $grants ): bool {
	if ( $grants ) {
		update_option( WPAGENT_COMPANION_OPT_WC_ORDER_GRANTS, $grants, false );
	} else {
		delete_option( WPAGENT_COMPANION_OPT_WC_ORDER_GRANTS );
	}
	return wpagent_wc_order_capability_grants() === $grants;
}

function wpagent_wc_orders_available(): bool {
	return function_exists( 'wc_get_orders' )
		&& function_exists( 'wc_get_order' )
		&& function_exists( 'wc_get_order_notes' )
		&& function_exists( 'wc_get_order_note' )
		&& class_exists( 'WC_Order' );
}

function wpagent_wc_orders_missing_error(): WP_Error {
	return new WP_Error(
		'wpagent_wc_orders_missing',
		__( 'WooCommerce ist nicht aktiv oder seine Bestell-CRUD-Schnittstelle ist nicht vollständig verfügbar.', 'wp-agent-companion' ),
		array( 'status' => 400 )
	);
}

/**
 * WooCommerce maps limit=-1 to MySQL's unsigned 64-bit maximum. The official
 * SQLite integration rejects that value because SQLite accepts only signed
 * 64-bit LIMIT values. PHP's integer maximum preserves the unbounded query
 * semantics on supported WordPress hosts without changing MySQL.
 *
 * @param array<string,mixed> $args
 * @return array<string,mixed>
 */
function wpagent_wc_bound_unlimited_refund_query_for_sqlite( array $args ): array {
	global $wpdb;
	if ( ! $wpdb instanceof WP_SQLite_DB || -1 !== (int) ( $args['limit'] ?? 0 ) ) {
		return $args;
	}
	$types = array_map( 'sanitize_key', (array) ( $args['type'] ?? $args['post_type'] ?? array() ) );
	if ( in_array( 'shop_order_refund', $types, true ) ) {
		$args['limit'] = PHP_INT_MAX;
	}
	return $args;
}
add_filter( 'woocommerce_order_query_args', 'wpagent_wc_bound_unlimited_refund_query_for_sqlite', 20 );

/**
 * Ability GET and DELETE requests transport nested booleans as query strings.
 * Accept only the explicit true representations so that "false" can never
 * enable personal or payment data by being a non-empty string.
 *
 * @param mixed $value
 */
function wpagent_wc_input_is_true( $value ): bool {
	return true === $value || 1 === $value || '1' === $value || ( is_string( $value ) && 'true' === strtolower( trim( $value ) ) );
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_wc_order_user_access( int $user_id ) {
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_wc_order_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$capabilities = array_values(
		array_filter(
			wpagent_wc_order_required_capabilities(),
			static fn( string $capability ): bool => user_can( $user, $capability )
		)
	);
	$grants = wpagent_wc_order_capability_grants();
	return array(
		'enabled'      => count( $capabilities ) === count( wpagent_wc_order_required_capabilities() ),
		'capabilities' => $capabilities,
		'tracked'      => $grants[ $user_id ] ?? array(),
	);
}

/**
 * Orders contain personal, address, payment, and communication data. These
 * capabilities are never granted together with ordinary catalog access.
 *
 * @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error
 */
function wpagent_wc_set_order_user_access( int $user_id, bool $enabled ) {
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_wc_order_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( $enabled && ! wpagent_wc_orders_available() ) {
		return wpagent_wc_orders_missing_error();
	}
	$lock = wpagent_companion_acquire_connection_lock();
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$grants          = wpagent_wc_order_capability_grants();
		$original_grants = $grants;
		$tracked         = $grants[ $user_id ] ?? array();
		$newly_added     = array();
		if ( $enabled ) {
			foreach ( wpagent_wc_order_required_capabilities() as $capability ) {
				if ( in_array( $capability, $tracked, true ) ) {
					$user->add_cap( $capability );
					continue;
				}
				if ( ! user_can( $user, $capability ) ) {
					$user->add_cap( $capability );
					$tracked[]     = $capability;
					$newly_added[] = $capability;
				}
			}
			$tracked = array_values( array_unique( $tracked ) );
			if ( $tracked ) {
				$grants[ $user_id ] = $tracked;
			} else {
				unset( $grants[ $user_id ] );
			}
			if ( ! wpagent_wc_store_order_capability_grants( $grants ) ) {
				foreach ( $newly_added as $capability ) {
					$user->remove_cap( $capability );
				}
				wpagent_wc_store_order_capability_grants( $original_grants );
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_wc_order_grant_store_failed', __( 'Die WooCommerce-Bestellrechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		} else {
			foreach ( $tracked as $capability ) {
				$user->remove_cap( $capability );
			}
			clean_user_cache( $user_id );
			$fresh_user = get_user_by( 'id', $user_id );
			$remaining  = $fresh_user instanceof WP_User
				? array_values( array_intersect( $tracked, array_keys( array_filter( (array) $fresh_user->caps ) ) ) )
				: $tracked;
			if ( $remaining ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_wc_order_grant_remove_failed', __( 'Die von wpagently ergänzten WooCommerce-Bestellrechte konnten nicht vollständig entfernt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			unset( $grants[ $user_id ] );
			if ( ! wpagent_wc_store_order_capability_grants( $grants ) ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_wc_order_grant_store_failed', __( 'Die WooCommerce-Bestellrechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		clean_user_cache( $user_id );
		$access = wpagent_wc_order_user_access( $user_id );
		if ( $enabled && ( is_wp_error( $access ) || ! $access['enabled'] ) ) {
			foreach ( $newly_added as $capability ) {
				$user->remove_cap( $capability );
			}
			wpagent_wc_store_order_capability_grants( $original_grants );
			clean_user_cache( $user_id );
			return is_wp_error( $access ) ? $access : new WP_Error( 'wpagent_wc_order_grant_failed', __( 'Die WooCommerce-Bestellrechte konnten nicht vollständig aktiviert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return $access;
	} finally {
		wpagent_companion_release_connection_lock( $lock );
	}
}

/** @param mixed $input */
function wpagent_perm_wc_orders_read( $input = null ): bool {
	return current_user_can( 'read_private_shop_orders' );
}

/** @param mixed $input */
function wpagent_perm_wc_orders_edit( $input = null ): bool {
	return current_user_can( 'edit_shop_orders' );
}

/** @param mixed $value */
function wpagent_wc_order_date( $value ): ?string {
	return $value instanceof WC_DateTime ? $value->date( DATE_ATOM ) : null;
}

/** @return array<string,string> */
function wpagent_wc_order_address( WC_Order $order, string $type ): array {
	$address = $order->get_address( $type );
	$result  = array();
	foreach ( array( 'first_name', 'last_name', 'company', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country', 'email', 'phone' ) as $key ) {
		if ( array_key_exists( $key, $address ) ) {
			$result[ $key ] = sanitize_text_field( (string) $address[ $key ] );
		}
	}
	return $result;
}

/** @return list<array<string,mixed>> */
function wpagent_wc_order_items( WC_Order $order ): array {
	$items = array();
	foreach ( array_slice( $order->get_items( 'line_item' ), 0, 200, true ) as $item_id => $item ) {
		if ( ! $item instanceof WC_Order_Item_Product ) {
			continue;
		}
		$items[] = array(
			'item_id'      => absint( $item_id ),
			'product_id'   => $item->get_product_id(),
			'variation_id' => $item->get_variation_id(),
			'name'         => sanitize_text_field( $item->get_name() ),
			'quantity'     => (float) $item->get_quantity(),
			'subtotal'     => (string) $item->get_subtotal(),
			'subtotal_tax' => (string) $item->get_subtotal_tax(),
			'total'        => (string) $item->get_total(),
			'total_tax'    => (string) $item->get_total_tax(),
		);
	}
	return $items;
}

/** @return array<string,mixed> */
function wpagent_wc_order_payload( WC_Order $order, bool $include_customer = false, bool $include_payment = false, bool $include_items = false ): array {
	$line_items = $order->get_items( 'line_item' );
	$payload    = array(
		'provider'        => 'woocommerce',
		'order_id'        => $order->get_id(),
		'number'          => (string) $order->get_order_number(),
		'status'          => $order->get_status(),
		'currency'        => $order->get_currency(),
		'date_created'    => wpagent_wc_order_date( $order->get_date_created() ),
		'date_modified'   => wpagent_wc_order_date( $order->get_date_modified() ),
		'date_completed'  => wpagent_wc_order_date( $order->get_date_completed() ),
		'total'           => (string) $order->get_total(),
		'total_tax'       => (string) $order->get_total_tax(),
		'shipping_total'  => (string) $order->get_shipping_total(),
		'discount_total'  => (string) $order->get_discount_total(),
		'refunded_total'  => (string) $order->get_total_refunded(),
		'item_count'      => count( $line_items ),
		'items_truncated' => count( $line_items ) > 200,
	);
	if ( $include_items ) {
		$payload['items'] = wpagent_wc_order_items( $order );
	}
	if ( $include_customer ) {
		$payload['customer'] = array(
			'customer_id'   => $order->get_customer_id(),
			'billing'       => wpagent_wc_order_address( $order, 'billing' ),
			'shipping'      => wpagent_wc_order_address( $order, 'shipping' ),
			'customer_note' => sanitize_textarea_field( $order->get_customer_note() ),
		);
	}
	if ( $include_payment ) {
		$payload['payment'] = array(
			'method'         => sanitize_text_field( $order->get_payment_method() ),
			'method_title'   => sanitize_text_field( $order->get_payment_method_title() ),
			'transaction_id' => sanitize_text_field( $order->get_transaction_id() ),
			'date_paid'      => wpagent_wc_order_date( $order->get_date_paid() ),
		);
	}
	return $payload;
}

/** @return array<string,mixed> */
function wpagent_wc_order_output_schema(): array {
	$nullable_string = array( 'type' => array( 'string', 'null' ) );
	$address_schema  = array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array_fill_keys(
			array( 'first_name', 'last_name', 'company', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country', 'email', 'phone' ),
			array( 'type' => 'string' )
		),
	);
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'provider'        => array( 'type' => 'string' ),
			'order_id'        => array( 'type' => 'integer' ),
			'number'          => array( 'type' => 'string' ),
			'status'          => array( 'type' => 'string' ),
			'currency'        => array( 'type' => 'string' ),
			'date_created'    => $nullable_string,
			'date_modified'   => $nullable_string,
			'date_completed'  => $nullable_string,
			'total'           => array( 'type' => 'string' ),
			'total_tax'       => array( 'type' => 'string' ),
			'shipping_total'  => array( 'type' => 'string' ),
			'discount_total'  => array( 'type' => 'string' ),
			'refunded_total'  => array( 'type' => 'string' ),
			'item_count'      => array( 'type' => 'integer' ),
			'items_truncated' => array( 'type' => 'boolean' ),
			'items'           => array(
				'type'  => 'array',
				'items' => array(
					'type'                 => 'object',
					'additionalProperties' => false,
					'properties'           => array(
						'item_id'      => array( 'type' => 'integer' ),
						'product_id'   => array( 'type' => 'integer' ),
						'variation_id' => array( 'type' => 'integer' ),
						'name'         => array( 'type' => 'string' ),
						'quantity'     => array( 'type' => 'number' ),
						'subtotal'     => array( 'type' => 'string' ),
						'subtotal_tax' => array( 'type' => 'string' ),
						'total'        => array( 'type' => 'string' ),
						'total_tax'    => array( 'type' => 'string' ),
					),
				),
			),
			'customer'        => array(
				'type'                 => 'object',
				'additionalProperties' => false,
				'properties'           => array(
					'customer_id'   => array( 'type' => 'integer' ),
					'billing'       => $address_schema,
					'shipping'      => $address_schema,
					'customer_note' => array( 'type' => 'string' ),
				),
			),
			'payment'         => array(
				'type'                 => 'object',
				'additionalProperties' => false,
				'properties'           => array(
					'method'         => array( 'type' => 'string' ),
					'method_title'   => array( 'type' => 'string' ),
					'transaction_id' => array( 'type' => 'string' ),
					'date_paid'      => $nullable_string,
				),
			),
		),
		'required'             => array( 'provider', 'order_id', 'number', 'status', 'currency', 'date_created', 'date_modified', 'date_completed', 'total', 'total_tax', 'shipping_total', 'discount_total', 'refunded_total', 'item_count', 'items_truncated' ),
	);
}

/** @return array<string,mixed> */
function wpagent_wc_order_note_payload( object $note ): array {
	return array(
		'note_id'       => absint( $note->id ?? 0 ),
		'order_id'      => absint( $note->order_id ?? 0 ),
		'date_created'  => wpagent_wc_order_date( $note->date_created ?? null ),
		'content'       => sanitize_textarea_field( wp_strip_all_tags( (string) ( $note->content ?? '' ) ) ),
		'customer_note' => ! empty( $note->customer_note ),
		'added_by'      => sanitize_text_field( (string) ( $note->added_by ?? '' ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_wc_order_note_output_schema(): array {
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'note_id'       => array( 'type' => 'integer' ),
			'order_id'      => array( 'type' => 'integer' ),
			'date_created'  => array( 'type' => array( 'string', 'null' ) ),
			'content'       => array( 'type' => 'string' ),
			'customer_note' => array( 'type' => 'boolean' ),
			'added_by'      => array( 'type' => 'string' ),
		),
		'required'             => array( 'note_id', 'order_id', 'date_created', 'content', 'customer_note', 'added_by' ),
	);
}

/** @param mixed $input @return WC_Order|WP_Error */
function wpagent_wc_get_authorized_order( $input, string $capability ) {
	if ( ! wpagent_wc_orders_available() ) {
		return wpagent_wc_orders_missing_error();
	}
	$input    = is_array( $input ) ? $input : array();
	$order_id = absint( $input['order_id'] ?? 0 );
	if ( $order_id <= 0 ) {
		return new WP_Error( 'wpagent_wc_bad_order_id', __( 'order_id muss eine positive Ganzzahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! current_user_can( $capability ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung für diese WooCommerce-Bestellung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$order = wc_get_order( $order_id );
	if ( ! $order instanceof WC_Order || $order instanceof WC_Order_Refund ) {
		return new WP_Error( 'wpagent_wc_order_not_found', __( 'WooCommerce-Bestellung nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return $order;
}

/** @return string|WP_Error */
function wpagent_wc_normalize_order_status( $raw, bool $for_write = false ) {
	$status = sanitize_key( (string) $raw );
	$status = str_starts_with( $status, 'wc-' ) ? substr( $status, 3 ) : $status;
	$known  = array_map( static fn( string $key ): string => str_starts_with( $key, 'wc-' ) ? substr( $key, 3 ) : $key, array_keys( wc_get_order_statuses() ) );
	if ( '' === $status || ! in_array( $status, $known, true ) ) {
		return new WP_Error( 'wpagent_wc_bad_order_status', __( 'Der angeforderte Bestellstatus ist nicht registriert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( $for_write && in_array( $status, array( 'refunded', 'checkout-draft' ), true ) ) {
		return new WP_Error( 'wpagent_wc_unsafe_order_status', __( 'Erstattet und Checkout-Entwurf dürfen nicht als bloßer Status gesetzt werden. Eine Erstattung benötigt einen eigenen Zahlungs- und Betragsvorgang.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $status;
}

function wpagent_wc_order_expected_version_matches( array $input, WC_Order $order ): bool {
	$expected = trim( (string) ( $input['expected_date_modified'] ?? '' ) );
	$actual   = wpagent_wc_order_date( $order->get_date_modified() ) ?? '';
	return '' === $expected || hash_equals( $expected, $actual );
}
