<?php
/**
 * Central permission resolver for order-status restrictions.
 *
 * @package WOSR
 */

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

class WOSR_Permissions {

	/** @var array|null */
	private static $status_rules = null;

	/** @var array|null */
	private static $overview_rules = null;

	/** @var array|null */
	private static $registered_statuses = null;

	/** @var array */
	private static $permission_cache = array();

	/** Normalize an order status to WooCommerce's wc-* format. */
	public static function normalize_status( $status ) {
		$status = sanitize_key( $status );
		return 0 === strpos( $status, 'wc-' ) ? $status : 'wc-' . $status;
	}

	/** Return the registered status targeted by an order-row action. */
	public static function get_action_target_status( $key, $action ) {
		if ( ! empty( $action['url'] ) ) {
			$query = wp_parse_url( $action['url'], PHP_URL_QUERY );
			if ( $query ) {
				parse_str( $query, $parameters );
				foreach ( array( 'status', 'order_status', 'new_status', 'to_status', 'wc_order_status' ) as $parameter ) {
					if ( ! empty( $parameters[ $parameter ] ) ) {
						$target = self::match_registered_status( $parameters[ $parameter ] );
						if ( null !== $target ) {
							return $target;
						}
					}
				}
			}
		}

		$known = array(
			'complete'   => 'wc-completed',
			'completed'  => 'wc-completed',
			'processing' => 'wc-processing',
			'on-hold'    => 'wc-on-hold',
			'cancelled'  => 'wc-cancelled',
			'refunded'   => 'wc-refunded',
			'failed'     => 'wc-failed',
		);
		if ( isset( $known[ $key ] ) ) {
			return $known[ $key ];
		}

		$candidates = array( $key );
		if ( ! empty( $action['action'] ) ) {
			$candidates = array_merge( $candidates, preg_split( '/\s+/', (string) $action['action'] ) );
		}
		foreach ( array_filter( $candidates ) as $candidate ) {
			$target = self::match_registered_status( $candidate );
			if ( null !== $target ) {
				return $target;
			}
		}
		return null;
	}

	/** Resolve a bulk-action key to a registered order status. */
	public static function get_bulk_target_status( $action ) {
		$action = sanitize_key( $action );
		if ( 0 !== strpos( $action, 'mark_' ) ) {
			return null;
		}
		return self::match_registered_status( substr( $action, 5 ) );
	}

	/** Whether a user is exempt from restrictions. */
	public static function is_exempt( $user ) {
		return ! $user instanceof WP_User || ! $user->ID || in_array( 'administrator', (array) $user->roles, true );
	}

	/**
	 * Get statuses allowed by matching role/payment rules.
	 * Null means no matching rule; an empty array means nothing is allowed.
	 */
	public static function get_allowed_statuses( $order, $user ) {
		if ( ! $order instanceof WC_Order || self::is_exempt( $user ) ) {
			return null;
		}
		$cache_key = implode( ':', array( (int) $user->ID, implode( ',', (array) $user->roles ), (int) $order->get_id(), $order->get_status(), $order->get_payment_method() ) );
		if ( array_key_exists( $cache_key, self::$permission_cache ) ) {
			return self::$permission_cache[ $cache_key ];
		}

		$allowed = array();
		$matched = false;
		foreach ( self::get_status_rules() as $rule ) {
			if ( ! array_intersect( (array) $user->roles, $rule['roles'] ) || ! self::payment_method_matches( $order->get_payment_method(), $rule['methods'] ) ) {
				continue;
			}
			$matched = true;
			$allowed = array_merge( $allowed, $rule['statuses'] );
		}
		self::$permission_cache[ $cache_key ] = $matched ? self::normalize_allowed_statuses( $allowed ) : null;
		return self::$permission_cache[ $cache_key ];
	}

	/** Check a transition against every applicable rule. */
	public static function status_transition_is_allowed( $order, $target_status, $user ) {
		if ( self::normalize_status( $target_status ) === 'wc-' . $order->get_status() ) {
			return true;
		}
		$allowed = self::get_allowed_statuses( $order, $user );
		return null === $allowed || in_array( self::normalize_status( $target_status ), $allowed, true );
	}

	/** Get order-list overview statuses for all matching user roles. */
	public static function get_overview_statuses( $user ) {
		if ( self::is_exempt( $user ) ) {
			return null;
		}
		$allowed = array();
		$matched = false;
		foreach ( self::get_overview_rules() as $rule ) {
			if ( ! array_intersect( (array) $user->roles, $rule['roles'] ) ) {
				continue;
			}
			$matched = true;
			$allowed = array_merge( $allowed, $rule['statuses'] );
		}
		if ( ! $matched ) {
			return null;
		}
		$allowed = array_diff( array_map( 'sanitize_key', $allowed ), array( 'nothing-allowed' ) );
		return array_values( array_unique( $allowed ) );
	}

	/** Sanitize roles against the current WordPress role registry. */
	public static function sanitize_roles( $input ) {
		$roles = array_map( 'sanitize_key', (array) wp_unslash( $input ) );
		$roles = array_intersect( array_unique( $roles ), array_keys( wp_roles()->roles ) );
		return array_values( array_diff( $roles, array( 'administrator' ) ) );
	}

	/** Sanitize payment methods against active WooCommerce gateways. */
	public static function sanitize_payment_methods( $input ) {
		$valid = array( 'all_payment_methods' );
		if ( function_exists( 'WC' ) && WC()->payment_gateways() ) {
			$valid = array_merge( $valid, array_keys( WC()->payment_gateways()->payment_gateways() ) );
		}
		$methods = array_values( array_intersect( array_unique( array_map( 'sanitize_key', (array) wp_unslash( $input ) ) ), $valid ) );
		return in_array( 'all_payment_methods', $methods, true ) ? array( 'all_payment_methods' ) : $methods;
	}

	/** Sanitize statuses against registered WooCommerce statuses. */
	public static function sanitize_statuses( $input ) {
		$valid = array_merge( array_keys( wc_get_order_statuses() ), array( 'nothing-allowed' ) );
		$statuses = array_values( array_intersect( array_unique( array_map( 'sanitize_key', (array) wp_unslash( $input ) ) ), $valid ) );
		return in_array( 'nothing-allowed', $statuses, true ) ? array( 'nothing-allowed' ) : $statuses;
	}

	/** Clear same-request memoized rules after a settings mutation. */
	public static function clear_request_cache() {
		self::$status_rules    = null;
		self::$overview_rules  = null;
		self::$permission_cache = array();
	}

	private static function match_registered_status( $candidate ) {
		$candidate = str_replace( '_', '-', sanitize_key( $candidate ) );
		$candidate = preg_replace( '/^(?:wc-|mark-|status-)/', '', $candidate );
		if ( null === self::$registered_statuses ) {
			self::$registered_statuses = array();
			foreach ( array_keys( wc_get_order_statuses() ) as $status ) {
				$normalized = self::normalize_status( $status );
				self::$registered_statuses[ str_replace( '_', '-', preg_replace( '/^wc-/', '', $normalized ) ) ] = $normalized;
			}
		}
		return isset( self::$registered_statuses[ $candidate ] ) ? self::$registered_statuses[ $candidate ] : null;
	}

	private static function get_status_rules() {
		if ( null !== self::$status_rules ) {
			return self::$status_rules;
		}
		global $wpdb;
		$table = $wpdb->prefix . 'wosr_order_status';
		$rows  = $wpdb->get_results( $wpdb->prepare( "SELECT user_role, payment_method, statuses FROM {$table} WHERE status = %s ORDER BY id DESC LIMIT 2", 'enabled' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		self::$status_rules = array();
		foreach ( $rows as $row ) {
			$roles = json_decode( $row->user_role, true );
			$methods = json_decode( $row->payment_method, true );
			$statuses = json_decode( $row->statuses, true );
			if ( is_array( $roles ) && is_array( $methods ) && is_array( $statuses ) ) {
				self::$status_rules[] = array( 'roles' => $roles, 'methods' => $methods, 'statuses' => $statuses );
			}
		}
		return self::$status_rules;
	}

	private static function get_overview_rules() {
		if ( null !== self::$overview_rules ) {
			return self::$overview_rules;
		}
		global $wpdb;
		$table = $wpdb->prefix . 'wosr_order_status_overview';
		$rows  = $wpdb->get_results( $wpdb->prepare( "SELECT user_role, statuses FROM {$table} WHERE status = %s ORDER BY id DESC LIMIT 2", 'enabled' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		self::$overview_rules = array();
		foreach ( $rows as $row ) {
			$roles = json_decode( $row->user_role, true );
			$statuses = json_decode( $row->statuses, true );
			if ( is_array( $roles ) && is_array( $statuses ) ) {
				self::$overview_rules[] = array( 'roles' => $roles, 'statuses' => $statuses );
			}
		}
		return self::$overview_rules;
	}

	private static function normalize_allowed_statuses( $statuses ) {
		$result = array();
		foreach ( array_unique( (array) $statuses ) as $status ) {
			$status = sanitize_key( $status );
			if ( 'nothing-allowed' !== $status && 0 === strpos( $status, 'wc-' ) ) {
				$result[] = $status;
			}
		}
		return array_values( array_unique( $result ) );
	}

	private static function payment_method_matches( $payment_method, $methods ) {
		return in_array( $payment_method, $methods, true ) || in_array( 'all_payment_methods', $methods, true );
	}
}
