<?php
/** Verified WooCommerce order status and note writes. */

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

/** @return array<string,mixed> */
function wpagent_wc_order_write_output_schema(): array {
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'order'    => wpagent_wc_order_output_schema(),
			'verified' => array( 'type' => 'boolean' ),
		),
		'required'             => array( 'order', 'verified' ),
	);
}

function wpagent_ability_set_order_status(): array {
	return array(
		'label'               => __( 'WooCommerce-Bestellstatus setzen', 'wp-agent-companion' ),
		'description'         => __( 'Setzt einen registrierten Bestellstatus über WooCommerce CRUD mit frischem Zustands-Hash, scoped lock und Read-back. Die Änderung kann E-Mails, Lagerbewegungen, Webhooks und Drittanbieter-Automationen auslösen und erfordert confirm=true. Ein Erstattungsstatus wird ohne eigenen Erstattungsvorgang abgelehnt.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'                 => 'object',
			'additionalProperties' => false,
			'properties'           => array(
				'order_id'               => array( 'type' => 'integer', 'minimum' => 1 ),
				'status'                 => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 64 ),
				'expected_state_hash'    => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'expected_date_modified' => array( 'type' => 'string', 'maxLength' => 40 ),
				'confirm'                => array( 'type' => 'boolean' ),
			),
			'required'             => array( 'order_id', 'status', 'expected_state_hash', 'confirm' ),
		),
		'output_schema'       => wpagent_wc_order_write_output_schema(),
		'execute_callback'    => 'wpagent_exec_set_order_status',
		'permission_callback' => 'wpagent_perm_wc_orders_edit',
		'meta'                => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => true, 'idempotent' => false ) ),
	);
}

/** @param mixed $input @return array|WP_Error */
function wpagent_exec_set_order_status( $input ) {
	$input = is_array( $input ) ? $input : array();
	if ( ! wpagent_wc_input_is_true( $input['confirm'] ?? false ) ) {
		return new WP_Error( 'wpagent_confirmation_required', __( 'Bestellstatusänderungen erfordern confirm=true, weil sie Kunden-E-Mails, Lagerbewegungen, Webhooks oder Drittanbieter-Automationen auslösen können.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$status = wpagent_wc_normalize_order_status( $input['status'] ?? '', true );
	if ( is_wp_error( $status ) ) {
		return $status;
	}
	$order = wpagent_wc_get_authorized_order( $input, 'edit_shop_orders' );
	if ( is_wp_error( $order ) ) {
		return $order;
	}
	if ( ! wpagent_wc_order_expected_version_matches( $input, $order ) ) {
		return new WP_Error( 'wpagent_wc_order_conflict', __( 'Die WooCommerce-Bestellung wurde seit dem gelesenen Stand verändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$lock = wpagent_wc_acquire_order_lock( $order->get_id() );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$fresh = wpagent_wc_fresh_order( $order->get_id() );
		if ( is_wp_error( $fresh ) ) {
			return $fresh;
		}
		if ( ! $fresh instanceof WC_Order ) {
			return new WP_Error( 'wpagent_wc_order_not_found', __( 'WooCommerce-Bestellung nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		}
		if ( ! wpagent_wc_order_expected_version_matches( $input, $fresh ) ) {
			return new WP_Error( 'wpagent_wc_order_conflict', __( 'Die WooCommerce-Bestellung wurde seit dem gelesenen Stand verändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		if ( $fresh->get_status() === $status ) {
			$payload = wpagent_wc_order_read_payload( $fresh );
			return is_wp_error( $payload ) ? $payload : array( 'order' => $payload, 'verified' => true );
		}
		if ( ! wpagent_wc_order_lock_checkpoint( $fresh->get_id(), $lock ) ) {
			return new WP_Error( 'wpagent_wc_order_recovery_required', __( 'Die exklusive Bestellsperre ging vor dem Statuswechsel verloren. Lies die Bestellung neu und prüfe ihren Zustand manuell.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) );
		}
		$save_started = false;
		try {
			$fresh->set_status( $status );
			$save_started = true;
			$saved_id = $fresh->save();
		} catch ( Throwable $error ) {
			return $save_started
				? new WP_Error( 'wpagent_wc_order_recovery_required', __( 'WooCommerce hat beim Bestellstatuswechsel einen Fehler ausgelöst. Wiederhole ihn nicht automatisch, weil E-Mails, Lagerbewegungen oder Webhooks bereits ausgelöst worden sein können. Lies die Bestellung neu und prüfe sie manuell.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) )
				: new WP_Error( 'wpagent_wc_order_status_failed', __( 'Der WooCommerce-Bestellstatus konnte nicht sicher vorbereitet werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		if ( ! wpagent_wc_order_lock_checkpoint( $fresh->get_id(), $lock ) ) {
			return new WP_Error( 'wpagent_wc_order_recovery_required', __( 'Die exklusive Bestellsperre ging nach dem Statuswechsel verloren. Wiederhole ihn nicht automatisch und prüfe die Bestellung manuell.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) );
		}
		$readback = $saved_id ? wpagent_wc_fresh_order( $fresh->get_id() ) : false;
		if ( ! $readback instanceof WC_Order || $readback->get_status() !== $status ) {
			return new WP_Error( 'wpagent_wc_order_recovery_required', __( 'Die Bestellstatusänderung konnte nicht sicher verifiziert werden. WPAgently führt kein automatisches Rollback aus, weil dadurch E-Mails, Lagerbewegungen oder Webhooks ein zweites Mal ausgelöst werden könnten. Lies die Bestellung neu und prüfe sie manuell.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) );
		}
		if ( ! wpagent_wc_order_lock_checkpoint( $readback->get_id(), $lock ) ) {
			return new WP_Error( 'wpagent_wc_order_recovery_required', __( 'Die exklusive Bestellsperre ging während des Status-Read-back verloren. Wiederhole den Vorgang nicht automatisch und prüfe die Bestellung manuell.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) );
		}
		$payload = wpagent_wc_order_read_payload( $readback );
		if ( is_wp_error( $payload ) ) {
			return new WP_Error( 'wpagent_wc_order_recovery_required', __( 'Die geänderte Bestellung verletzt den sicheren Lesevertrag. Wiederhole den Statuswechsel nicht automatisch und prüfe die Bestellung manuell.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) );
		}
		return array( 'order' => $payload, 'verified' => true );
	} finally {
		wpagent_wc_release_order_lock( $order->get_id(), $lock );
	}
}

/** @return array<string,mixed> */
function wpagent_wc_order_note_write_output_schema(): array {
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'note'     => wpagent_wc_order_note_output_schema(),
			'verified' => array( 'type' => 'boolean' ),
			'reused'   => array( 'type' => 'boolean' ),
		),
		'required'             => array( 'note', 'verified', 'reused' ),
	);
}

function wpagent_ability_add_order_note(): array {
	return array(
		'label'               => __( 'WooCommerce-Bestellnotiz hinzufügen', 'wp-agent-companion' ),
		'description'         => __( 'Fügt eine Nur-Text-Bestellnotiz über WooCommerce hinzu und verifiziert sie. Kundensichtbare Notizen können eine E-Mail auslösen und benötigen eine ausdrückliche Bestätigung. Ein Idempotenzschlüssel verhindert doppelte Notizen bei Wiederholungen.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'                 => 'object',
			'additionalProperties' => false,
			'properties'           => array(
				'order_id'                      => array( 'type' => 'integer', 'minimum' => 1 ),
				// The central REST guard applies maxLength to UTF-8 bytes. Four bytes
				// per character preserves the documented 2,000-character contract.
				'note'                          => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => WPAGENT_WC_ORDER_MAX_NOTE_BYTES ),
				'customer_note'                 => array( 'type' => 'boolean', 'default' => false ),
				'confirm_customer_notification' => array( 'type' => 'boolean', 'default' => false ),
				'idempotency_key'               => array( 'type' => 'string', 'minLength' => 8, 'maxLength' => 64, 'pattern' => '^[A-Za-z0-9._:-]+$' ),
			),
			'required'             => array( 'order_id', 'note', 'idempotency_key' ),
		),
		'output_schema'       => wpagent_wc_order_note_write_output_schema(),
		'execute_callback'    => 'wpagent_exec_add_order_note',
		'permission_callback' => 'wpagent_perm_wc_orders_edit',
		'meta'                => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => true, 'idempotent' => true ) ),
	);
}

/** @return WP_Comment[] */
function wpagent_wc_find_order_notes_by_idempotency_key( int $order_id, string $idempotency_key ): array {
	$woocommerce_filter = array( 'WC_Comments', 'exclude_order_comments' );
	$filter_was_active  = has_filter( 'comments_clauses', $woocommerce_filter );
	if ( false !== $filter_was_active ) {
		remove_filter( 'comments_clauses', $woocommerce_filter, (int) $filter_was_active );
	}
	try {
		$comments = get_comments(
			array(
				'post_id'    => $order_id,
				'type'       => 'order_note',
				'status'     => 'approve',
				'number'     => 2,
				'meta_key'   => '_wpagent_idempotency_key',
				'meta_value' => $idempotency_key,
			)
		);
		return array_values( array_filter( $comments, static fn( $comment ): bool => $comment instanceof WP_Comment ) );
	} finally {
		if ( false !== $filter_was_active ) {
			add_filter( 'comments_clauses', $woocommerce_filter, (int) $filter_was_active );
		}
	}
}

function wpagent_wc_order_note_character_length( string $value ): int {
	$bytes      = strlen( $value );
	$characters = 0;
	for ( $index = 0; $index < $bytes; $index++ ) {
		if ( ( ord( $value[ $index ] ) & 0xC0 ) !== 0x80 ) {
			$characters++;
		}
	}
	return $characters;
}

/**
 * Reads the complete identity and metadata of an exact WPAgently-created note.
 *
 * WooCommerce exposes no conditional comment deletion. We therefore use this full
 * provider and database snapshot only to decide whether a later compensation can
 * safely remove the exact unchanged comment that this invocation created.
 *
 * @return array{note:object,state_hash:string}|false
 */
function wpagent_wc_order_note_owned_candidate( int $note_id, int $order_id, string $expected_note, bool $expected_customer_note, string $idempotency_key, string $payload_hash ) {
	if ( $note_id < 1 || $order_id < 1 || ! preg_match( '/^[A-Za-z0-9._:-]{8,64}$/D', $idempotency_key ) || ! preg_match( '/^[a-f0-9]{64}$/D', $payload_hash ) ) {
		return false;
	}
	global $wpdb;
	$comment = $wpdb->get_row(
		$wpdb->prepare( "SELECT * FROM {$wpdb->comments} WHERE comment_ID = %d", $note_id ),
		ARRAY_A
	);
	$meta = $wpdb->get_results(
		$wpdb->prepare( "SELECT meta_id, meta_key, meta_value FROM {$wpdb->commentmeta} WHERE comment_id = %d ORDER BY meta_id ASC", $note_id ),
		ARRAY_A
	);
	if ( ! is_array( $comment ) || ! is_array( $meta ) || $note_id !== (int) ( $comment['comment_ID'] ?? 0 ) || $order_id !== (int) ( $comment['comment_post_ID'] ?? 0 ) || 'order_note' !== (string) ( $comment['comment_type'] ?? '' ) || '1' !== (string) ( $comment['comment_approved'] ?? '' ) || $expected_note !== sanitize_textarea_field( wp_strip_all_tags( (string) ( $comment['comment_content'] ?? '' ) ) ) ) {
		return false;
	}
	$idempotency_matches = 0;
	$payload_matches     = 0;
	$canonical_meta      = array();
	foreach ( $meta as $row ) {
		if ( ! is_array( $row ) || ! isset( $row['meta_id'], $row['meta_key'], $row['meta_value'] ) || ! is_scalar( $row['meta_id'] ) || ! ctype_digit( (string) $row['meta_id'] ) || ! is_string( $row['meta_key'] ) || ! is_string( $row['meta_value'] ) ) {
			return false;
		}
		if ( '_wpagent_idempotency_key' === $row['meta_key'] && hash_equals( $idempotency_key, $row['meta_value'] ) ) {
			$idempotency_matches++;
		}
		if ( '_wpagent_payload_hash' === $row['meta_key'] && hash_equals( $payload_hash, $row['meta_value'] ) ) {
			$payload_matches++;
		}
		$canonical_meta[] = array(
			'meta_id'    => (int) $row['meta_id'],
			'meta_key'   => $row['meta_key'],
			'meta_value' => $row['meta_value'],
		);
	}
	if ( 1 !== $idempotency_matches || 1 !== $payload_matches ) {
		return false;
	}
	clean_comment_cache( $note_id );
	$note = wc_get_order_note( $note_id );
	if ( ! is_object( $note ) || $order_id !== absint( $note->order_id ?? 0 ) || $expected_note !== sanitize_textarea_field( wp_strip_all_tags( (string) ( $note->content ?? '' ) ) ) || $expected_customer_note !== ! empty( $note->customer_note ) ) {
		return false;
	}
	$state = array(
		'comment_ID'             => (int) ( $comment['comment_ID'] ?? 0 ),
		'comment_agent'          => (string) ( $comment['comment_agent'] ?? '' ),
		'comment_approved'       => (string) ( $comment['comment_approved'] ?? '' ),
		'comment_author'         => (string) ( $comment['comment_author'] ?? '' ),
		'comment_author_IP'      => (string) ( $comment['comment_author_IP'] ?? '' ),
		'comment_author_email'   => (string) ( $comment['comment_author_email'] ?? '' ),
		'comment_author_url'     => (string) ( $comment['comment_author_url'] ?? '' ),
		'comment_content'        => (string) ( $comment['comment_content'] ?? '' ),
		'comment_date'           => (string) ( $comment['comment_date'] ?? '' ),
		'comment_date_gmt'       => (string) ( $comment['comment_date_gmt'] ?? '' ),
		'comment_karma'          => (string) ( $comment['comment_karma'] ?? '' ),
		'comment_parent'         => (int) ( $comment['comment_parent'] ?? 0 ),
		'comment_post_ID'        => (int) ( $comment['comment_post_ID'] ?? 0 ),
		'comment_type'           => (string) ( $comment['comment_type'] ?? '' ),
		'meta'                   => $canonical_meta,
		'user_id'                => (int) ( $comment['user_id'] ?? 0 ),
	);
	$encoded = wp_json_encode( $state, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( ! is_string( $encoded ) || '' === $encoded ) {
		return false;
	}
	return array( 'note' => $note, 'state_hash' => hash( 'sha256', $encoded ) );
}

/**
 * Permanently removes only the unchanged, exact note created by this invocation.
 *
 */
function wpagent_wc_compensate_owned_order_note( int $note_id, int $order_id, string $expected_note, bool $expected_customer_note, string $idempotency_key, string $payload_hash, string $expected_state_hash, string $lock ): bool {
	if ( ! preg_match( '/^[a-f0-9]{64}$/D', $expected_state_hash ) || ! wpagent_wc_order_lock_checkpoint( $order_id, $lock ) ) {
		return false;
	}
	$fresh = wpagent_wc_order_note_owned_candidate( $note_id, $order_id, $expected_note, $expected_customer_note, $idempotency_key, $payload_hash );
	if ( ! is_array( $fresh ) || ! isset( $fresh['state_hash'] ) || ! is_string( $fresh['state_hash'] ) || ! hash_equals( $expected_state_hash, $fresh['state_hash'] ) ) {
		return false;
	}
	try {
		// WooCommerce offers no compare-and-delete primitive. Keep the exact readback adjacent to deletion.
		$deleted = wp_delete_comment( $note_id, true );
	} catch ( Throwable $error ) {
		return false;
	}
	return false !== $deleted && ! get_comment( $note_id );
}

/** @return WP_Error */
function wpagent_wc_order_note_recovery_required(): WP_Error {
	return new WP_Error( 'wpagent_wc_order_note_recovery_required', __( 'Die WooCommerce-Bestellnotiz konnte nicht sicher verifiziert oder bereinigt werden. Lies die Bestellung neu und prüfe die Notiz manuell.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) );
}

/** @param mixed $input @return array|WP_Error */
function wpagent_exec_add_order_note( $input ) {
	$input = is_array( $input ) ? $input : array();
	$order = wpagent_wc_get_authorized_order( $input, 'edit_shop_orders' );
	if ( is_wp_error( $order ) ) {
		return $order;
	}
	$note = sanitize_textarea_field( (string) ( $input['note'] ?? '' ) );
	if ( '' === trim( $note ) || strlen( $note ) > WPAGENT_WC_ORDER_MAX_NOTE_BYTES || wpagent_wc_order_note_character_length( $note ) > WPAGENT_WC_ORDER_MAX_NOTE_CHARACTERS ) {
		return new WP_Error( 'wpagent_wc_bad_order_note', __( 'note muss nach der Nur-Text-Bereinigung zwischen 1 und 2.000 Zeichen enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$customer_note = wpagent_wc_input_is_true( $input['customer_note'] ?? false );
	if ( $customer_note && ! wpagent_wc_input_is_true( $input['confirm_customer_notification'] ?? false ) ) {
		return new WP_Error( 'wpagent_confirmation_required', __( 'Eine kundensichtbare Bestellnotiz kann eine E-Mail auslösen und erfordert confirm_customer_notification=true.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$idempotency_key = (string) ( $input['idempotency_key'] ?? '' );
	if ( ! preg_match( '/^[A-Za-z0-9._:-]{8,64}$/', $idempotency_key ) ) {
		return new WP_Error( 'wpagent_wc_bad_idempotency_key', __( 'idempotency_key muss 8 bis 64 Zeichen aus Buchstaben, Zahlen, Punkt, Unterstrich, Doppelpunkt oder Bindestrich enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$payload_hash = hash( 'sha256', wp_json_encode( array( 'note' => $note, 'customer_note' => $customer_note ) ) );
	$lock         = wpagent_wc_acquire_order_lock( $order->get_id() );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$fresh = wpagent_wc_fresh_order( $order->get_id() );
		if ( is_wp_error( $fresh ) ) {
			return $fresh;
		}
		if ( ! $fresh instanceof WC_Order || $fresh instanceof WC_Order_Refund ) {
			return new WP_Error( 'wpagent_wc_order_not_found', __( 'WooCommerce-Bestellung nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		}
		if ( ! wpagent_wc_order_lock_checkpoint( $fresh->get_id(), $lock ) ) {
			return wpagent_wc_order_note_recovery_required();
		}
		$existing = wpagent_wc_find_order_notes_by_idempotency_key( $fresh->get_id(), $idempotency_key );
		if ( count( $existing ) > 1 ) {
			return new WP_Error( 'wpagent_wc_duplicate_idempotency_key', __( 'Der Idempotenzschlüssel ist dieser Bestellung mehrfach zugeordnet und muss manuell bereinigt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		if ( 1 === count( $existing ) ) {
			$comment = $existing[0];
			$replay = wpagent_wc_order_note_owned_candidate( $comment->comment_ID, $fresh->get_id(), $note, $customer_note, $idempotency_key, $payload_hash );
			if ( ! is_array( $replay ) || ! isset( $replay['note'] ) || ! is_object( $replay['note'] ) ) {
				return new WP_Error( 'wpagent_wc_idempotency_conflict', __( 'Der Idempotenzschlüssel wurde bereits mit einer anderen Bestellnotiz verwendet.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			if ( ! wpagent_wc_order_lock_checkpoint( $fresh->get_id(), $lock ) ) {
				return wpagent_wc_order_note_recovery_required();
			}
			$payload = wpagent_wc_order_note_read_payload( $replay['note'], $fresh->get_id() );
			return is_wp_error( $payload ) ? $payload : array( 'note' => $payload, 'verified' => true, 'reused' => true );
		}

		if ( ! wpagent_wc_order_lock_checkpoint( $fresh->get_id(), $lock ) ) {
			return wpagent_wc_order_note_recovery_required();
		}
		$note_id          = 0;
		$owned_state_hash = '';
		try {
			$created = $fresh->add_order_note(
				$note,
				$customer_note ? 1 : 0,
				true,
				array(
					'_wpagent_idempotency_key' => $idempotency_key,
					'_wpagent_payload_hash'    => $payload_hash,
				)
			);
			if ( ! is_int( $created ) && ! ( is_string( $created ) && ctype_digit( $created ) ) ) {
				return wpagent_wc_order_note_recovery_required();
			}
			$note_id = (int) $created;
			if ( $note_id < 1 || ! wpagent_wc_order_lock_checkpoint( $fresh->get_id(), $lock ) ) {
				return wpagent_wc_order_note_recovery_required();
			}
			$owned = wpagent_wc_order_note_owned_candidate( $note_id, $fresh->get_id(), $note, $customer_note, $idempotency_key, $payload_hash );
			if ( ! is_array( $owned ) || ! isset( $owned['note'], $owned['state_hash'] ) || ! is_object( $owned['note'] ) || ! is_string( $owned['state_hash'] ) ) {
				return wpagent_wc_order_note_recovery_required();
			}
			$owned_state_hash = $owned['state_hash'];
			if ( ! wpagent_wc_order_lock_checkpoint( $fresh->get_id(), $lock ) ) {
				return wpagent_wc_order_note_recovery_required();
			}
			$payload = wpagent_wc_order_note_read_payload( $owned['note'], $fresh->get_id() );
			if ( is_wp_error( $payload ) ) {
				throw new RuntimeException( 'WooCommerce order-note readback violates the safe output contract.' );
			}
			return array( 'note' => $payload, 'verified' => true, 'reused' => false );
		} catch ( Throwable $error ) {
			if ( $note_id > 0 && '' !== $owned_state_hash && wpagent_wc_compensate_owned_order_note( $note_id, $fresh->get_id(), $note, $customer_note, $idempotency_key, $payload_hash, $owned_state_hash, $lock ) ) {
				return new WP_Error( 'wpagent_wc_order_note_verify_failed', __( 'Die Bestellnotiz konnte nach dem Speichern nicht vollständig verarbeitet werden und wurde nach einer exakten Eigentumsprüfung entfernt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			return wpagent_wc_order_note_recovery_required();
		}
	} finally {
		wpagent_wc_release_order_lock( $order->get_id(), $lock );
	}
}
