<?php

namespace WooNooW\Api;

use WP_REST_Request;
use WP_REST_Response;
use Automattic\WooCommerce\Utilities\OrderUtil;
use WooNooW\Core\ModuleRegistry;

class OrdersController
{
  /**
   * Sanitize and validate a text field
   * Returns empty string if value is empty, whitespace-only, or invalid
   */
  private static function sanitize_field($value)
  {
    if (! isset($value) || $value === '') {
      return '';
    }

    $sanitized = sanitize_text_field($value);
    $trimmed = trim($sanitized);

    // Return empty if only whitespace or sanitization removed everything
    return $trimmed !== '' ? $trimmed : '';
  }

  /**
   * Sanitize phone number
   * Removes non-numeric characters except +, spaces, and hyphens
   * Returns empty string if result is invalid
   */
  private static function sanitize_phone($phone)
  {
    if (! isset($phone) || $phone === '') {
      return '';
    }

    // Remove non-numeric characters except + and spaces/hyphens
    $phone = preg_replace('/[^0-9+\s-]/', '', $phone);
    $phone = trim($phone);

    // If result is empty or just symbols, return empty
    if ($phone === '' || preg_match('/^[+\s-]+$/', $phone)) {
      return '';
    }

    return $phone;
  }

  /**
   * Sanitize email
   * Returns empty string if invalid email
   */
  private static function sanitize_email_field($email)
  {
    if (! isset($email) || $email === '') {
      return '';
    }

    $sanitized = sanitize_email($email);
    return is_email($sanitized) ? $sanitized : '';
  }

  public static function init()
  {
    // Register action hook for delayed email sending
    add_action('woonoow/send_order_email', [__CLASS__, 'send_order_email'], 10, 1);

    // Hook into order status changes to schedule our delayed emails
    add_action('woocommerce_order_status_changed', [__CLASS__, 'on_order_status_changed'], 10, 4);
  }

  public static function register()
  {
    register_rest_route('woonoow/v1', '/orders', [
      'methods'             => 'GET',
      'callback'            => [__CLASS__, 'index'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
      'args' => [
        'status'     => ['type' => 'string',  'required' => false], // comma-separated
        'page'       => ['type' => 'integer', 'default'  => 1],
        'per_page'   => ['type' => 'integer', 'default'  => 25],
        'date_start' => ['type' => 'string',  'required' => false], // YYYY-MM-DD or ISO8601
        'date_end'   => ['type' => 'string',  'required' => false],
        'orderby'    => ['type' => 'string',  'default'  => 'date'],
        'order'      => ['type' => 'string',  'default'  => 'desc'], // asc|desc
      ],
    ]);

    register_rest_route('woonoow/v1', '/orders/(?P<id>\d+)', [
      'methods'             => 'GET',
      'callback'            => [__CLASS__, 'show'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
    ]);

    register_rest_route('woonoow/v1', '/orders/(?P<id>\d+)', [
      'methods'             => 'PATCH',
      'callback'            => [__CLASS__, 'update'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
      'args' => [
        'items'           => ['type' => 'array',  'required' => false],
        'billing'         => ['type' => 'object', 'required' => false],
        'shipping'        => ['type' => 'object', 'required' => false],
        'status'          => ['type' => 'string', 'required' => false],
        'payment_method'  => ['type' => 'string', 'required' => false],
        'shipping_method' => ['type' => 'string', 'required' => false],
        'coupons'         => ['type' => 'array',  'required' => false],
        'note'            => ['type' => 'string', 'required' => false],
      ],
    ]);

    register_rest_route('woonoow/v1', '/orders', [
      'methods'             => 'POST',
      'callback'            => [__CLASS__, 'create'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
      'args' => [
        'items'           => ['type' => 'array',  'required' => true],
        'billing'         => ['type' => 'object', 'required' => false],
        'shipping'        => ['type' => 'object', 'required' => false],
        'status'          => ['type' => 'string', 'required' => false],
        'payment_method'  => ['type' => 'string', 'required' => false],
        'shipping_method' => ['type' => 'string', 'required' => false], // e.g. flat_rate:1
        'coupons'         => ['type' => 'array',  'required' => false], // array of codes
        'note'            => ['type' => 'string', 'required' => false],
      ],
    ]);

    register_rest_route('woonoow/v1', '/orders/(?P<id>\d+)', [
      'methods'             => 'DELETE',
      'callback'            => [__CLASS__, 'delete'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
    ]);

    // Lightweight product search to help build orders from admin UI
    // Changed from /products to /products/search to avoid conflict with ProductsController
    register_rest_route('woonoow/v1', '/products/search', [
      'methods'             => 'GET',
      'callback'            => [__CLASS__, 'products'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
      'args' => [
        'search' => ['type' => 'string', 'required' => false],
        'limit'  => ['type' => 'integer', 'default' => 10],
      ],
    ]);

    register_rest_route('woonoow/v1', '/payments', [
      'methods'             => 'GET',
      'callback'            => [__CLASS__, 'payments'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
    ]);

    register_rest_route('woonoow/v1', '/shippings', [
      'methods'             => 'GET',
      'callback'            => [__CLASS__, 'shippings'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
    ]);

    // Calculate shipping rates for given address and cart
    register_rest_route('woonoow/v1', '/shipping/calculate', [
      'methods'             => 'POST',
      'callback'            => [__CLASS__, 'calculate_shipping'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
      'args' => [
        'items'    => ['type' => 'array',  'required' => true],
        'shipping' => ['type' => 'object', 'required' => true],
      ],
    ]);

    // Calculate order preview with taxes and totals
    register_rest_route('woonoow/v1', '/orders/preview', [
      'methods'             => 'POST',
      'callback'            => [__CLASS__, 'preview_order'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
      'args' => [
        'items'           => ['type' => 'array',  'required' => true],
        'billing'         => ['type' => 'object', 'required' => false],
        'shipping'        => ['type' => 'object', 'required' => false],
        'shipping_method' => ['type' => 'string', 'required' => false],
        'coupons'         => ['type' => 'array',  'required' => false],
      ],
    ]);

    // Retry payment processing for an order
    register_rest_route('woonoow/v1', '/orders/(?P<id>\d+)/retry-payment', [
      'methods'             => 'POST',
      'callback'            => [__CLASS__, 'retry_payment'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
    ]);

    // Validate coupon code
    register_rest_route('woonoow/v1', '/coupons/validate', [
      'methods'             => 'POST',
      'callback'            => [__CLASS__, 'validate_coupon'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
      'args' => [
        'code' => [
          'required' => true,
          'type'     => 'string',
        ],
        'items' => [
          'required' => false,
          'type'     => 'array',
        ],
        'subtotal' => [
          'required' => false,
          'type'     => 'number',
        ],
      ],
    ]);

    register_rest_route('woonoow/v1', '/countries', [
      'methods'             => 'GET',
      'callback'            => [__CLASS__, 'countries'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
    ]);

    // Customer search for autofill
    register_rest_route('woonoow/v1', '/customers/search', [
      'methods'             => 'GET',
      'callback'            => [__CLASS__, 'search_customers'],
      'permission_callback' => function () {
        return current_user_can('manage_woocommerce');
      },
      'args' => [
        'email' => [
          'required' => false,
          'type'     => 'string',
        ],
        'search' => [
          'required' => false,
          'type'     => 'string',
        ],
      ],
    ]);
  }

  /**
   * GET /woonoow/v1/orders
   */
  public static function index(WP_REST_Request $req): WP_REST_Response
  {
    $page     = max(1, absint($req->get_param('page') ?? 1));
    $per_page = max(1, min(200, absint($req->get_param('per_page') ?? 20)));

    $orderby  = sanitize_key($req->get_param('orderby') ?? 'date');
    $order    = strtoupper($req->get_param('order') ?? 'DESC');
    if (! in_array($orderby, ['date', 'id', 'modified', 'total'], true)) $orderby = 'date';
    if ($order !== 'ASC') $order = 'DESC';

    $status   = $req->get_param('status');
    // If status is missing, include ALL Woo statuses.
    $statuses = $status
      ? array_map(function ($s) {
        $s = sanitize_key(trim($s));
        return 'wc-' . preg_replace('/^wc-/', '', $s);
      }, explode(',', $status))
      : array_keys(wc_get_order_statuses());

    // --- Build date range (HPOS-safe, string values) ---
    $date_start = $req->get_param('date_start');
    $date_end   = $req->get_param('date_end');

    $after_str  = null;
    $before_str = null;

    if ($date_start) {
      // Expecting YYYY-MM-DD; build 00:00:00 UTC
      $after_ts = strtotime($date_start . ' 00:00:00');
      if ($after_ts) $after_str = gmdate('Y-m-d H:i:s', $after_ts);
    }
    if ($date_end) {
      // Build 23:59:59 UTC
      $before_ts = strtotime($date_end . ' 23:59:59');
      if ($before_ts) $before_str = gmdate('Y-m-d H:i:s', $before_ts);
    }

    $date_created = null;
    if ($after_str || $before_str) {
      $date_created = array_filter([
        'after'  => $after_str,
        'before' => $before_str,
        // note: do NOT include 'inclusive' to avoid Woo date arg coercion issues
      ]);
    }

    $args = [
      'paginate'     => true,
      'page'         => $page,
      'limit'        => $per_page,
      'status'       => $statuses,
      'orderby'      => $orderby === 'total' ? 'total' : $orderby, // Woo supports 'total' in recent versions
      'order'        => $order,
      'return'       => 'ids', // HPOS friendly + fast
    ];

    // Guard: Some Woo installs throw when passing array date ranges into OrdersTableQuery.
    // To be safe, only pass a single-bound filter directly. For two-sided ranges, we post-filter below.
    $pass_single_bound = $date_created && (
      (isset($date_created['after']) && ! isset($date_created['before'])) ||
      (isset($date_created['before']) && ! isset($date_created['after']))
    );
    if ($pass_single_bound) {
      $args['date_created'] = $date_created; // single bound only
    }

    // Optional simple ID search
    $search = $req->get_param('search');
    if ($search && preg_match('/^#?(\\d+)$/', $search, $m)) {
      $args['include'] = [absint($m[1])];
    }

    // Optional customer filter
    $customer_id = $req->get_param('customer_id');
    if ($customer_id) {
      $args['customer_id'] = absint($customer_id);
    }

    $result = wc_get_orders($args);

    // If we had a two-sided date range, filter results manually to avoid OrdersTableQuery fatal.
    if ($date_created && isset($date_created['after']) && isset($date_created['before'])) {
      $after_ts  = strtotime($date_created['after']);
      $before_ts = strtotime($date_created['before']);
      if ($after_ts && $before_ts) {
        $result->orders = array_values(array_filter($result->orders, function ($order_id) use ($after_ts, $before_ts) {
          $o = wc_get_order($order_id);
          if (! $o || ! $o->get_date_created()) return false;
          $t = $o->get_date_created()->getTimestamp();
          return ($t >= $after_ts && $t <= $before_ts);
        }));
        $result->total = count($result->orders);
      }
    }

    $rows = array_map(function ($order_id) {
      $o = wc_get_order($order_id);

      // Customer name: prefer billing first/last; otherwise WP user's display name; otherwise null
      $first = trim((string) $o->get_billing_first_name());
      $last  = trim((string) $o->get_billing_last_name());
      $name  = trim($first . ' ' . $last);
      if ($name === '') {
        $uid = $o->get_user_id();
        if ($uid) {
          $u = get_userdata($uid);
          if ($u && ! empty($u->display_name)) {
            $name = (string) $u->display_name;
          }
        }
        if ($name === '') {
          $name = null; // no email/Guest fallback per requirement
        }
      }

      // Items count (qty-based) and brief label
      $items       = $o->get_items();
      $items_count = 0;
      $brief_parts = [];
      $i = 0;
      foreach ($items as $it) {
        $qty = (int) $it->get_quantity();
        $items_count += $qty;
        if ($i < 2) {
          $brief_parts[] = sprintf('%s ×%d', $it->get_name(), $qty);
        }
        $i++;
      }
      $items_brief = implode(', ', $brief_parts);
      $lines_total = count($items);
      $lines_preview = min(2, $lines_total);
      if ($lines_total > 2) {
        $items_brief .= sprintf(' +%d more', $lines_total - 2);
      }

      // Full list (name ×qty), used by hover card
      $full_parts = [];
      foreach ($items as $it_full) {
        $full_parts[] = sprintf('%s ×%d', $it_full->get_name(), (int) $it_full->get_quantity());
      }
      $items_full = implode(', ', $full_parts);

      // Build site-local ISO and epoch seconds for created date
      $dt_created = $o->get_date_created();
      $date_ts  = $dt_created ? $dt_created->getTimestamp() : null; // epoch seconds (UTC)
      $tz_obj   = wp_timezone();
      $tz_str   = wp_timezone_string();
      $date_iso = null;
      if ($dt_created) {
        $local = clone $dt_created;
        $local = $local->setTimezone($tz_obj);
        $date_iso = $local->format(DATE_ATOM); // e.g. 2025-10-25T00:23:53+07:00
      }

      return [
        'id'            => $o->get_id(),
        'number'        => $o->get_order_number(),
        'date'          => $date_iso,
        'date_ts'       => $date_ts,
        'tz'            => $tz_str,
        'status'        => $o->get_status(),
        'total'         => (float) $o->get_total(),
        'currency'      => $o->get_currency(),
        'currency_symbol' => get_woocommerce_currency_symbol($o->get_currency()),
        'customer'      => $name,
        'items_count'   => $items_count,
        'items_brief'   => $items_brief,
        'items_full'    => $items_full,
        'lines_total'   => $lines_total,
        'lines_preview' => $lines_preview,
      ];
    }, $result->orders);

    return new WP_REST_Response([
      'rows' => $rows,
      'total' => (int) $result->total,
      'page'  => (int) $page,
      'per_page' => (int) $per_page,
    ], 200);
  }

  /**
   * GET /woonoow/v1/orders/{id}
   */
  public static function show(WP_REST_Request $req): WP_REST_Response
  {
    $id = absint($req['id']);
    if (! $id) return new WP_REST_Response(['error' => 'invalid_id'], 400);

    $order = wc_get_order($id);
    if (! $order) return new WP_REST_Response(['error' => 'not_found'], 404);

    $items = [];
    foreach ($order->get_items() as $item) {
      /** @var \WC_Order_Item_Product $item */
      $product  = $item->get_product();
      $qty      = (int) $item->get_quantity();
      $subtotal = (float) $item->get_subtotal();
      $total    = (float) $item->get_total();
      $product_id = $product ? $product->get_id() : 0;
      $unit_price = $qty > 0 ? (float) ($subtotal / $qty) : 0.0;
      $items[] = [
        'line_item_id' => $item->get_id(),
        'id'           => $item->get_id(),
        'product_id'   => $product_id,
        'name'         => $item->get_name(),
        'sku'          => $product ? $product->get_sku() : '',
        'qty'          => $qty,
        'price'        => $unit_price,
        'subtotal'     => $subtotal,
        'total'        => $total,
        'subtotal_fmt' => wc_price($subtotal, ['currency' => $order->get_currency()]),
        'total_fmt'    => wc_price($total,    ['currency' => $order->get_currency()]),
        'virtual'      => $product ? $product->is_virtual() : false,
        'downloadable' => $product ? $product->is_downloadable() : false,
        'regular_price' => $product ? (float) $product->get_regular_price() : 0,
        'sale_price'   => $product && $product->get_sale_price() ? (float) $product->get_sale_price() : null,
      ];
    }

    $notes = [];
    foreach (wc_get_order_notes(['order_id' => $id, 'orderby' => 'date_created', 'order' => 'DESC']) as $note) {
      $notes[] = [
        'id' => $note->id,
        'content' => $note->content,
        'is_customer_note' => (bool) $note->customer_note,
        'date' => $note->date_created ? $note->date_created->date(DATE_ATOM) : null,
      ];
    }

    $coupon_codes = [];
    foreach ($order->get_items('coupon') as $citem) {
      $coupon_codes[] = (string) $citem->get_code();
    }

    $dt_created = $order->get_date_created();
    $date_ts  = $dt_created ? $dt_created->getTimestamp() : null;
    $tz_obj   = wp_timezone();
    $tz_str   = wp_timezone_string();
    $date_iso = null;
    if ($dt_created) {
      $local = clone $dt_created;
      $local = $local->setTimezone($tz_obj);
      $date_iso = $local->format(DATE_ATOM);
    }

    // Defensive address formatting (Woo helpers may not be loaded in rare contexts)
    $billing_addr_arr  = $order->get_address('billing');
    $shipping_addr_arr = $order->get_address('shipping');

    // Build raw billing/shipping fields for editing
    $billing_fields = [
      'first_name' => (string) $order->get_billing_first_name(),
      'last_name'  => (string) $order->get_billing_last_name(),
      'email'      => (string) $order->get_billing_email(),
      'phone'      => (string) $order->get_billing_phone(),
      'address_1'  => (string) $order->get_billing_address_1(),
      'address_2'  => (string) $order->get_billing_address_2(),
      'city'       => (string) $order->get_billing_city(),
      'state'      => (string) $order->get_billing_state(),
      'postcode'   => (string) $order->get_billing_postcode(),
      'country'    => (string) $order->get_billing_country(),
    ];
    if ($billing_fields['first_name'] === '' && $billing_fields['last_name'] === '') {
      $uid = $order->get_user_id();
      if ($uid) {
        $u = get_userdata($uid);
        if ($u && ! empty($u->display_name)) {
          $parts = preg_split('/\s+/', trim((string) $u->display_name), 2);
          $billing_fields['first_name'] = (string) ($parts[0] ?? '');
          $billing_fields['last_name']  = (string) ($parts[1] ?? '');
        }
      }
    }

    $shipping_fields = [
      'first_name' => (string) $order->get_shipping_first_name(),
      'last_name'  => (string) $order->get_shipping_last_name(),
      'phone'      => '', // Woo core doesn't store shipping phone by default
      'address_1'  => (string) $order->get_shipping_address_1(),
      'address_2'  => (string) $order->get_shipping_address_2(),
      'city'       => (string) $order->get_shipping_city(),
      'state'      => (string) $order->get_shipping_state(),
      'postcode'   => (string) $order->get_shipping_postcode(),
      'country'    => (string) $order->get_shipping_country(),
    ];

    if (function_exists('wc') && is_object(wc()) && isset(wc()->countries) && method_exists(wc()->countries, 'get_formatted_address')) {
      $billing_addr_html  = wc()->countries->get_formatted_address($billing_addr_arr);
      $shipping_addr_html = wc()->countries->get_formatted_address($shipping_addr_arr);
    } elseif (function_exists('wc_format_address')) {
      $billing_addr_html  = \wc_format_address($billing_addr_arr);
      $shipping_addr_html = \wc_format_address($shipping_addr_arr);
    } else {
      // Fallback: simple HTML join
      $fmt = function ($a) {
        $parts = [
          trim(($a['first_name'] ?? '') . ' ' . ($a['last_name'] ?? '')),
          $a['address_1'] ?? '',
          $a['address_2'] ?? '',
          trim((($a['city'] ?? '')) . (($a['state'] ?? '') ? ', ' . $a['state'] : '') . (($a['postcode'] ?? '') ? ' ' . $a['postcode'] : '')),
          $a['country'] ?? '',
        ];
        $parts = array_values(array_filter($parts, fn($v) => $v !== ''));
        return implode('<br>', $parts);
      };
      $billing_addr_html  = $fmt($billing_addr_arr);
      $shipping_addr_html = $fmt($shipping_addr_arr);
    }

    $data = [
      'id'        => $order->get_id(),
      'number'    => $order->get_order_number(),
      'status'    => $order->get_status(),
      'currency'  => $order->get_currency(),
      'currency_symbol'  => get_woocommerce_currency_symbol($order->get_currency()),
      'currency_display' => get_woocommerce_currency_symbol($order->get_currency()),
      'currency_pos'    => get_option('woocommerce_currency_pos', 'left'),
      'decimals'        => wc_get_price_decimals(),
      'decimal_sep'     => wc_get_price_decimal_separator(),
      'thousand_sep'    => wc_get_price_thousand_separator(),
      'date'      => $date_iso,
      'date_ts'   => $date_ts,
      'tz'        => $tz_str,
      'payment_method'       => self::get_payment_method_title($order),
      'payment_method_id'    => $order->get_payment_method(),
      'payment_meta'         => self::get_payment_metadata($order),
      'shipping_method'      => self::get_shipping_method_title($order),
      'shipping_method_id'   => self::get_shipping_method_id($order),
      'totals' => [
        'subtotal'     => (float) $order->get_subtotal(),
        'discount'     => (float) $order->get_discount_total(),
        'shipping'     => (float) $order->get_shipping_total(),
        'tax'          => (float) $order->get_total_tax(),
        'total'        => (float) $order->get_total(),
        'subtotal_fmt' => wc_price((float) $order->get_subtotal(),      ['currency' => $order->get_currency()]),
        'discount_fmt' => wc_price((float) $order->get_discount_total(), ['currency' => $order->get_currency()]),
        'shipping_fmt' => wc_price((float) $order->get_shipping_total(), ['currency' => $order->get_currency()]),
        'tax_fmt'      => wc_price((float) $order->get_total_tax(),      ['currency' => $order->get_currency()]),
        'total_fmt'    => wc_price((float) $order->get_total(),          ['currency' => $order->get_currency()]),
      ],
      'billing' => array_merge($billing_fields, [
        'name'         => trim($billing_fields['first_name'] . ' ' . $billing_fields['last_name']),
        'email'        => $billing_fields['email'],
        'phone'        => $billing_fields['phone'],
        'address_html' => $billing_addr_html,
      ]),
      'shipping' => array_merge($shipping_fields, [
        'name'         => trim($shipping_fields['first_name'] . ' ' . $shipping_fields['last_name']),
        'address_html' => $shipping_addr_html,
      ]),
      'items' => $items,
      'notes' => $notes,
      'coupons' => $coupon_codes,
      'customer_note' => $order->get_customer_note(),
      'meta' => self::get_order_meta_data($order),
    ];

    // Get related subscription
    if (ModuleRegistry::is_enabled('subscription')) {
      if (class_exists('\WooNooW\Modules\Subscription\SubscriptionManager')) {
        $subscription = \WooNooW\Modules\Subscription\SubscriptionManager::get_by_order_id($id);
        if ($subscription) {
          $period_labels = [
            'day' => __('day', 'woonoow'),
            'week' => __('week', 'woonoow'),
            'month' => __('month', 'woonoow'),
            'year' => __('year', 'woonoow'),
          ];
          $interval = $subscription->billing_interval > 1 ? $subscription->billing_interval . ' ' : '';
          $period = $period_labels[$subscription->billing_period] ?? $subscription->billing_period;
          if ($subscription->billing_interval > 1) {
            $period .= 's'; // Pluralize
          }

          $data['related_subscription'] = [
            'id'                => (int) $subscription->id,
            'status'            => $subscription->status,
            'next_payment_date' => $subscription->next_payment_date,
            'billing_period'    => $subscription->billing_period,
            'billing_interval'  => (int) $subscription->billing_interval,
            'recurring_amount'  => (float) $subscription->recurring_amount,
            'billing_schedule'  => sprintf(__('Every %s%s', 'woonoow'), $interval, $period),
          ];
        }
      }
    }

    // Get related licenses
    if (ModuleRegistry::is_enabled('licensing')) {
      if (class_exists('\WooNooW\Modules\Licensing\LicenseManager')) {
        $licenses = \WooNooW\Modules\Licensing\LicenseManager::get_licenses_by_order($id);
        if (! empty($licenses)) {
          $data['related_licenses'] = array_map(function ($lic) {
            $product = wc_get_product($lic['product_id']);
            return [
              'id'           => (int) $lic['id'],
              'license_key'  => $lic['license_key'],
              'status'       => $lic['status'],
              'product_id'   => (int) $lic['product_id'],
              'product_name' => $product ? $product->get_name() : __('Unknown Product', 'woonoow'),
            ];
          }, $licenses);
        }
      }
    }

    // Get related affiliate referral
    if (ModuleRegistry::is_enabled('affiliate')) {
      if (class_exists('\WooNooW\Modules\Affiliate\AffiliateTracker')) {
        global $wpdb;
        $referral = \WooNooW\Modules\Affiliate\AffiliateTracker::get_referral_for_order($id);
        if ($referral) {
          $affiliates_table = $wpdb->prefix . 'woonoow_affiliates';
          $affiliate = $wpdb->get_row($wpdb->prepare(
            "SELECT a.*, u.display_name as affiliate_name, u.user_email as affiliate_email
             FROM $affiliates_table a
             LEFT JOIN $wpdb->users u ON a.user_id = u.ID
             WHERE a.id = %d",
            $referral->affiliate_id
          ));

          if ($affiliate) {
            $data['affiliate'] = [
              'has_referral'      => true,
              'referral_id'       => (int) $referral->id,
              'commission'        => (float) $referral->commission_amount,
              'currency'          => $referral->currency,
              'status'            => $referral->status,
              'cancelled_reason'  => $referral->cancelled_reason ?: null,
              'affiliate_id'      => (int) $affiliate->id,
              'affiliate_name'    => $affiliate->affiliate_name ?: __('Unknown', 'woonoow'),
              'affiliate_email'   => $affiliate->affiliate_email ?: '',
              'commission_rate'   => (float) $affiliate->commission_rate,
            ];
          }
        }
      }
    }

    // Allow plugins to modify response (Level 1 compatibility)
    $data = apply_filters('woonoow/order_api_data', $data, $order, $req);

    return new WP_REST_Response($data, 200);
  }

  /**
   * PATCH /woonoow/v1/orders/{id}
   * Upserts line items, replaces coupon lines, updates addresses & meta, then recalculates totals.
   */
  public static function update(WP_REST_Request $req): WP_REST_Response
  {
    if (! current_user_can('manage_woocommerce')) {
      return new \WP_REST_Response(['error' => 'forbidden'], 403);
    }

    $id = absint($req['id']);
    if (! $id) return new \WP_REST_Response(['error' => 'invalid_id'], 400);

    $order = wc_get_order($id);
    if (! $order) return new \WP_REST_Response(['error' => 'not_found'], 404);

    $p = $req->get_json_params();

    $items           = is_array($p['items'] ?? null) ? $p['items'] : null; // null means no change
    $billing         = is_array($p['billing'] ?? null) ? $p['billing'] : null;
    $shipping        = is_array($p['shipping'] ?? null) ? $p['shipping'] : null;
    $status          = array_key_exists('status', $p) ? sanitize_text_field((string) $p['status']) : null;
    $payment_method  = array_key_exists('payment_method', $p) ? sanitize_text_field((string) $p['payment_method']) : null;
    $shipping_method = array_key_exists('shipping_method', $p) ? sanitize_text_field((string) $p['shipping_method']) : null;
    $coupons         = array_key_exists('coupons', $p) ? array_filter(array_map('sanitize_text_field', (array) $p['coupons'])) : null;
    $note            = array_key_exists('customer_note', $p) ? wp_kses_post((string) $p['customer_note']) : null;

    try {
      // === Items upsert ===
      if (is_array($items)) {
        // Map existing line items by id for quick lookup
        $existing_items = $order->get_items('line_item');
        $existing_map = [];
        foreach ($existing_items as $it) {
          $existing_map[$it->get_id()] = $it;
        }

        $keep_ids = [];
        foreach ($items as $row) {
          $li_id = absint($row['line_item_id'] ?? 0);
          $qty   = max(1, absint($row['qty'] ?? 1));
          if ($li_id && isset($existing_map[$li_id])) {
            // Update quantity of existing line
            $existing_map[$li_id]->set_quantity($qty);
            $keep_ids[] = $li_id;
          } else {
            $pid = absint($row['product_id'] ?? 0);
            if ($pid) {
              $product = wc_get_product($pid);
              if ($product) {
                $order->add_product($product, $qty);
              }
            }
          }
        }
        // Delete removed items (present before, missing now)
        foreach ($existing_items as $it) {
          if (! in_array($it->get_id(), $keep_ids, true)) {
            $order->remove_item($it->get_id());
          }
        }
      }

      // === Billing ===
      if (is_array($billing)) {
        $order->set_address([
          'first_name' => self::sanitize_field($billing['first_name'] ?? ''),
          'last_name'  => self::sanitize_field($billing['last_name']  ?? ''),
          'email'      => self::sanitize_email_field($billing['email'] ?? ''),
          'phone'      => self::sanitize_phone($billing['phone'] ?? ''),
          'address_1'  => self::sanitize_field($billing['address_1']  ?? ''),
          'address_2'  => self::sanitize_field($billing['address_2']  ?? ''),
          'city'       => self::sanitize_field($billing['city']       ?? ''),
          'state'      => self::sanitize_field($billing['state']      ?? ''),
          'postcode'   => self::sanitize_field($billing['postcode']   ?? ''),
          'country'    => self::sanitize_field($billing['country']    ?? ''),
        ], 'billing');
      }

      // === Shipping ===
      if (is_array($shipping)) {
        $order->set_address([
          'first_name' => self::sanitize_field($shipping['first_name'] ?? ($shipping['name'] ?? '')),
          'last_name'  => self::sanitize_field($shipping['last_name']  ?? ''),
          'phone'      => self::sanitize_phone($shipping['phone']      ?? ''),
          'address_1'  => self::sanitize_field($shipping['address_1']  ?? ''),
          'address_2'  => self::sanitize_field($shipping['address_2']  ?? ''),
          'city'       => self::sanitize_field($shipping['city']       ?? ''),
          'state'      => self::sanitize_field($shipping['state']      ?? ''),
          'postcode'   => self::sanitize_field($shipping['postcode']   ?? ''),
          'country'    => self::sanitize_field($shipping['country']    ?? ''),
        ], 'shipping');
      }

      // === Shipping method line ===
      if (null !== $shipping_method) {
        // Remove existing shipping items
        foreach ($order->get_items('shipping') as $ship_item) {
          $order->remove_item($ship_item->get_id());
        }
        if ($shipping_method) {
          $method_id = $shipping_method;
          $instance_id = null;
          if (strpos($shipping_method, ':') !== false) {
            list($method_id, $instance_str) = explode(':', $shipping_method, 2);
            $instance_id = absint($instance_str);
          }
          $ship_item = new \WC_Order_Item_Shipping();
          $ship_item->set_method_id($method_id);
          if (null !== $instance_id && method_exists($ship_item, 'set_instance_id')) {
            $ship_item->set_instance_id($instance_id);
          }
          $ship_item->set_method_title($method_id);
          $ship_item->set_total(0);
          $order->add_item($ship_item);
        }
      }

      // === Payment method (info only) ===
      if (null !== $payment_method) {
        if ($payment_method) {
          $order->set_payment_method($payment_method);
        }
      }

      // === Coupons: replace ===
      if (is_array($coupons)) {
        // Remove existing coupon lines
        foreach ($order->get_items('coupon') as $citem) {
          $order->remove_item($citem->get_id());
        }
        foreach ($coupons as $code) {
          try {
            $coupon = new \WC_Coupon($code);
            if ($coupon && $coupon->get_code()) {
              $ci = new \WC_Order_Item_Coupon();
              $ci->set_code($coupon->get_code());
              $order->add_item($ci);
            }
          } catch (\Throwable $e) { /* ignore invalid */
          }
        }
      }

      // Customer note (visible to customer) - allow empty to clear note
      if (null !== $note) {
        $order->set_customer_note($note);
      }

      // Totals & status
      // Only recalculate if items/coupons/shipping changed; skip for status-only updates
      $needs_recalc = is_array($items) || is_array($coupons) || null !== $shipping_method;
      if ($needs_recalc) {
        $order->calculate_totals(true);
      }

      // PROFILING: Find what's causing the 30s delay
      if (null !== $status && $status !== '') {
        try {
          $order->set_status($status, 'Updated via API');
        } catch (\Throwable $e) {
          /* ignore invalid */
        }
      }

      // Update custom meta fields (Level 1 compatibility)
      if (isset($p['meta']) && is_array($p['meta'])) {
        self::update_order_meta_data($order, $p['meta']);
      }

      // SOLUTION: Block WooCommerce analytics tracking (pixel.wp.com) during save
      // This was causing 30s timeout on every order status change
      add_filter('pre_http_request', function ($preempt, $args, $url) {
        // Block WooCommerce analytics/tracking requests
        if (strpos($url, 'pixel.wp.com') !== false || strpos($url, 'stats.wp.com') !== false) {
          return new \WP_Error('http_request_blocked', 'WooCommerce analytics blocked during order save');
        }
        return $preempt;
      }, PHP_INT_MAX, 3);

      $order->save();

      // Allow plugins to perform additional updates (Level 1 compatibility)
      do_action('woonoow/order_updated', $order, $p, $req);

      // Clean up
      remove_all_filters('pre_http_request');

      // Schedule email for later
      if (null !== $status && $status !== '') {
        $order_id = $order->get_id();
        add_action('shutdown', function () use ($order_id, $status) {
          self::schedule_order_email($order_id, $status);
        }, 999);
      }

      return new \WP_REST_Response(['ok' => true, 'id' => $order->get_id()], 200);
    } catch (\Throwable $e) {
      // Log the actual error for debugging

      // Return user-friendly error message
      return new \WP_REST_Response([
        'error' => 'update_failed',
        'message' => __('Unable to update order. Please check all fields and try again.', 'woonoow')
      ], 500);
    }
  }

  /**
   * Hook handler for order status changes.
   * Schedules email notification with 15s delay.
   * ONLY runs outside of API requests (API requests schedule manually).
   */
  public static function on_order_status_changed($order_id, $status_from, $status_to, $order)
  {
    // Skip if we're in an API request (we schedule manually there)
    if (defined('REST_REQUEST') && REST_REQUEST) {
      return;
    }

    // Schedule email notification with 15s delay
    self::schedule_order_email($order_id, $status_to);
  }

  /**
   * Schedule order email notification with 15s delay (non-blocking).
   */
  private static function schedule_order_email(int $order_id, string $status)
  {
    if (function_exists('as_schedule_single_action')) {
      // Use Action Scheduler (preferred)
      as_schedule_single_action(time() + 15, 'woonoow/send_order_email', ['order_id' => $order_id, 'status' => $status], 'woonoow-emails');
    } else {
      // Fallback to wp-cron
      wp_schedule_single_event(time() + 15, 'woonoow/send_order_email', ['order_id' => $order_id, 'status' => $status]);
    }
  }

  /**
   * POST /woonoow/v1/orders
   */
  public static function create(WP_REST_Request $req): WP_REST_Response
  {
    if (! current_user_can('manage_woocommerce')) {
      return new \WP_REST_Response(['error' => 'forbidden'], 403);
    }

    $p = $req->get_json_params();

    $items           = is_array($p['items'] ?? null) ? $p['items'] : [];
    $billing         = is_array($p['billing'] ?? null) ? $p['billing'] : [];
    $shipping        = is_array($p['shipping'] ?? null) ? $p['shipping'] : [];
    $status          = sanitize_text_field($p['status'] ?? 'pending');
    $payment_method  = sanitize_text_field($p['payment_method'] ?? '');
    $shipping_method = sanitize_text_field($p['shipping_method'] ?? ''); // e.g. flat_rate:1
    $coupons         = array_filter(array_map('sanitize_text_field', (array) ($p['coupons'] ?? [])));
    $note            = isset($p['customer_note']) ? wp_kses_post((string) $p['customer_note']) : '';

    // Get auto-register setting from customer settings (site-level)
    $customer_settings = \WooNooW\Compat\CustomerSettingsProvider::get_settings();
    $register_member = $customer_settings['auto_register_members'] ?? false;

    // Validation: Collect all missing required fields
    $validation_errors = [];

    // 1. At least one product is required
    if (empty($items)) {
      $validation_errors[] = __('At least one product is required', 'woonoow');
    }

    // 2. Check if all products are virtual/downloadable
    $has_physical_product = false;
    foreach ($items as $item) {
      $product_id = absint($item['product_id'] ?? 0);
      if ($product_id) {
        $product = wc_get_product($product_id);
        if ($product && ! $product->is_virtual() && ! $product->is_downloadable()) {
          $has_physical_product = true;
          break;
        }
      }
    }

    // 3. Billing information required based on checkout fields configuration
    // Get checkout field settings to respect hidden/required status from PHP snippets
    $checkout_fields = apply_filters('woonoow/checkout/fields', [], $items);

    // Helper to check if a billing field is required
    $is_field_required = function ($field_key) use ($checkout_fields) {
      foreach ($checkout_fields as $field) {
        if (isset($field['key']) && $field['key'] === $field_key) {
          // Field is not required if hidden or explicitly not required
          if (! empty($field['hidden']) || $field['type'] === 'hidden') {
            return false;
          }
          return ! empty($field['required']);
        }
      }
      // Default: core fields are required if not found in API
      return true;
    };

    // Core billing fields - check against API configuration
    if ($is_field_required('billing_first_name') && empty($billing['first_name'])) {
      $validation_errors[] = __('Billing first name is required', 'woonoow');
    }
    if ($is_field_required('billing_last_name') && empty($billing['last_name'])) {
      $validation_errors[] = __('Billing last name is required', 'woonoow');
    }
    if ($is_field_required('billing_email') && empty($billing['email'])) {
      $validation_errors[] = __('Billing email is required', 'woonoow');
    }

    // Address fields only required for physical products AND if not hidden
    if ($has_physical_product) {
      if ($is_field_required('billing_address_1') && empty($billing['address_1'])) {
        $validation_errors[] = __('Billing address is required', 'woonoow');
      }
      if ($is_field_required('billing_city') && empty($billing['city'])) {
        $validation_errors[] = __('Billing city is required', 'woonoow');
      }
      if ($is_field_required('billing_postcode') && empty($billing['postcode'])) {
        $validation_errors[] = __('Billing postcode is required', 'woonoow');
      }
      if ($is_field_required('billing_country') && empty($billing['country'])) {
        $validation_errors[] = __('Billing country is required', 'woonoow');
      }
    }

    // 3. Validate email format if provided
    if (! empty($billing['email']) && ! is_email($billing['email'])) {
      $validation_errors[] = __('Billing email is not valid', 'woonoow');
    }

    // If there are validation errors, return them
    if (! empty($validation_errors)) {
      return new \WP_REST_Response([
        'error' => 'validation_failed',
        'message' => __('Please complete all required fields', 'woonoow'),
        'fields' => $validation_errors
      ], 400);
    }

    try {
      $order = wc_create_order();

      // Items
      foreach ($items as $row) {
        $pid = absint($row['product_id'] ?? 0);
        $qty = max(1, absint($row['qty'] ?? 1));
        if (! $pid) {
          continue;
        }
        $product = wc_get_product($pid);
        if (! $product) {
          continue;
        }
        $order->add_product($product, $qty);
      }

      // Billing
      if ($billing) {
        $order->set_address([
          'first_name' => self::sanitize_field($billing['first_name'] ?? ''),
          'last_name'  => self::sanitize_field($billing['last_name']  ?? ''),
          'email'      => self::sanitize_email_field($billing['email'] ?? ''),
          'phone'      => self::sanitize_phone($billing['phone'] ?? ''),
          'address_1'  => self::sanitize_field($billing['address_1']  ?? ''),
          'address_2'  => self::sanitize_field($billing['address_2']  ?? ''),
          'city'       => self::sanitize_field($billing['city']       ?? ''),
          'state'      => self::sanitize_field($billing['state']      ?? ''),
          'postcode'   => self::sanitize_field($billing['postcode']   ?? ''),
          'country'    => self::sanitize_field($billing['country']    ?? ''),
        ], 'billing');
      }

      // Shipping: copy billing unless explicit ship_to_different
      $ship_to_different = (bool) ($shipping['ship_to_different'] ?? false);
      $ship_addr = $ship_to_different ? $shipping : $billing;
      if ($ship_addr) {
        $order->set_address([
          'first_name' => self::sanitize_field($ship_addr['first_name'] ?? ($ship_addr['name'] ?? '')),
          'last_name'  => self::sanitize_field($ship_addr['last_name']  ?? ''),
          'phone'      => self::sanitize_phone($ship_addr['phone']      ?? ''),
          'address_1'  => self::sanitize_field($ship_addr['address_1']  ?? ''),
          'address_2'  => self::sanitize_field($ship_addr['address_2']  ?? ''),
          'city'       => self::sanitize_field($ship_addr['city']       ?? ''),
          'state'      => self::sanitize_field($ship_addr['state']      ?? ''),
          'postcode'   => self::sanitize_field($ship_addr['postcode']   ?? ''),
          'country'    => self::sanitize_field($ship_addr['country']    ?? ''),
        ], 'shipping');
      }

      // Shipping method - calculate using WooCommerce cart for accurate rates
      if ($shipping_method) {
        // Initialize cart if needed
        if (! WC()->cart) {
          wc_load_cart();
        }
        if (! WC()->session) {
          WC()->session = new \WC_Session_Handler();
          WC()->session->init();
        }

        // Temporarily use cart to calculate shipping
        WC()->cart->empty_cart();

        // Add items to cart
        foreach ($items as $row) {
          $pid = absint($row['product_id'] ?? 0);
          $qty = max(1, absint($row['qty'] ?? 1));
          if ($pid) {
            WC()->cart->add_to_cart($pid, $qty);
          }
        }

        // Set shipping address for calculation
        if ($ship_addr) {
          WC()->customer->set_shipping_country($ship_addr['country'] ?? '');
          WC()->customer->set_shipping_state($ship_addr['state'] ?? '');
          WC()->customer->set_shipping_postcode($ship_addr['postcode'] ?? '');
          WC()->customer->set_shipping_city($ship_addr['city'] ?? '');
          WC()->customer->set_shipping_address($ship_addr['address_1'] ?? '');
        }

        // Set chosen shipping method
        WC()->session->set('chosen_shipping_methods', [$shipping_method]);

        // Calculate shipping
        WC()->cart->calculate_shipping();
        WC()->cart->calculate_totals();

        // Get the calculated rate
        $packages = WC()->shipping()->get_packages();
        $shipping_cost = 0;
        $shipping_title = $shipping_method;
        $shipping_taxes = [];

        foreach ($packages as $package) {
          if (isset($package['rates'][$shipping_method])) {
            $rate = $package['rates'][$shipping_method];
            $shipping_cost = $rate->get_cost();
            $shipping_title = $rate->get_label();
            $shipping_taxes = $rate->get_taxes();
            break;
          }
        }

        // Clean up cart
        WC()->cart->empty_cart();

        // Parse method ID and instance ID
        $method_id = $shipping_method;
        $instance_id = null;
        if (strpos($shipping_method, ':') !== false) {
          list($method_id, $instance_str) = explode(':', $shipping_method, 2);
          $instance_id = absint($instance_str);
        }

        // Add shipping line item
        $ship_item = new \WC_Order_Item_Shipping();
        $ship_item->set_method_id($method_id);
        if (null !== $instance_id && method_exists($ship_item, 'set_instance_id')) {
          $ship_item->set_instance_id($instance_id);
        }
        $ship_item->set_method_title($shipping_title);
        $ship_item->set_total($shipping_cost);

        // Set shipping taxes if available
        if (! empty($shipping_taxes)) {
          $ship_item->set_taxes(['total' => $shipping_taxes]);
        }

        $order->add_item($ship_item);
      }

      // Payment method
      if ($payment_method) {
        $order->set_payment_method($payment_method);
      }

      // Coupons
      foreach ($coupons as $code) {
        try {
          $coupon = new \WC_Coupon($code);
          if ($coupon && $coupon->get_code()) {
            // Apply coupon to order (this will calculate discount)
            $order->apply_coupon($coupon);
          }
        } catch (\Throwable $e) {
        }
      }

      // Customer note (visible to customer)
      if ($note) {
        $order->set_customer_note($note);
      }

      // Calculate totals (includes products, shipping, coupons, taxes)
      $order->calculate_totals(true);

      // Set status after totals
      try {
        $order->set_status($status, 'Set via API');
      } catch (\Throwable $e) { /* ignore invalid */
      }

      // Block WooCommerce analytics tracking during save (same as update method)
      add_filter('pre_http_request', function ($preempt, $args, $url) {
        if (strpos($url, 'pixel.wp.com') !== false || strpos($url, 'stats.wp.com') !== false) {
          return new \WP_Error('http_request_blocked', 'WooCommerce analytics blocked during order save');
        }
        return $preempt;
      }, PHP_INT_MAX, 3);

      $order->save();

      // Clean up
      remove_all_filters('pre_http_request');

      // Process payment gateway if payment method is set and order is pending
      if ($payment_method && $status === 'pending') {
        self::process_payment_gateway($order, $payment_method);
      }

      // Link order to customer and update/create user account
      if (! empty($billing['email'])) {
        $email = sanitize_email($billing['email']);

        // Check if user already exists
        if (email_exists($email) || username_exists($email)) {
          // User exists - always link and update data
          $user = get_user_by('email', $email);
          if ($user) {
            // Upgrade subscriber to customer role (WooCommerce best practice)
            if (in_array('subscriber', (array) $user->roles, true)) {
              $user->set_role('customer');
            }

            // Update customer billing & shipping data
            $customer = new \WC_Customer($user->ID);

            // Update billing address - sanitize and validate each field
            $first_name = self::sanitize_field($billing['first_name'] ?? '');
            $last_name  = self::sanitize_field($billing['last_name'] ?? '');
            $email      = self::sanitize_email_field($billing['email'] ?? '');
            $phone      = self::sanitize_phone($billing['phone'] ?? '');
            $address_1  = self::sanitize_field($billing['address_1'] ?? '');
            $address_2  = self::sanitize_field($billing['address_2'] ?? '');
            $city       = self::sanitize_field($billing['city'] ?? '');
            $state      = self::sanitize_field($billing['state'] ?? '');
            $postcode   = self::sanitize_field($billing['postcode'] ?? '');
            $country    = self::sanitize_field($billing['country'] ?? '');

            // Only set if not empty (allow clearing fields by passing empty string)
            if ($first_name !== '') $customer->set_billing_first_name($first_name);
            if ($last_name !== '')  $customer->set_billing_last_name($last_name);
            if ($email !== '')      $customer->set_billing_email($email);
            if ($phone !== '')      $customer->set_billing_phone($phone);
            if ($address_1 !== '')  $customer->set_billing_address_1($address_1);
            if ($address_2 !== '')  $customer->set_billing_address_2($address_2);
            if ($city !== '')       $customer->set_billing_city($city);
            if ($state !== '')      $customer->set_billing_state($state);
            if ($postcode !== '')   $customer->set_billing_postcode($postcode);
            if ($country !== '')    $customer->set_billing_country($country);

            // Update shipping address (if provided)
            if (! empty($shipping) && is_array($shipping)) {
              $s_first_name = self::sanitize_field($shipping['first_name'] ?? '');
              $s_last_name  = self::sanitize_field($shipping['last_name'] ?? '');
              $s_address_1  = self::sanitize_field($shipping['address_1'] ?? '');
              $s_address_2  = self::sanitize_field($shipping['address_2'] ?? '');
              $s_city       = self::sanitize_field($shipping['city'] ?? '');
              $s_state      = self::sanitize_field($shipping['state'] ?? '');
              $s_postcode   = self::sanitize_field($shipping['postcode'] ?? '');
              $s_country    = self::sanitize_field($shipping['country'] ?? '');

              if ($s_first_name !== '') $customer->set_shipping_first_name($s_first_name);
              if ($s_last_name !== '')  $customer->set_shipping_last_name($s_last_name);
              if ($s_address_1 !== '')  $customer->set_shipping_address_1($s_address_1);
              if ($s_address_2 !== '')  $customer->set_shipping_address_2($s_address_2);
              if ($s_city !== '')       $customer->set_shipping_city($s_city);
              if ($s_state !== '')      $customer->set_shipping_state($s_state);
              if ($s_postcode !== '')   $customer->set_shipping_postcode($s_postcode);
              if ($s_country !== '')    $customer->set_shipping_country($s_country);
            }

            $customer->save();

            $order->set_customer_id($user->ID);
            $order->save();
          }
        } elseif ($register_member) {
          // User doesn't exist - create only if checkbox is checked
          // Generate password
          $password = wp_generate_password(12, true, true);

          // Prepare user data
          $userdata = [
            'user_login'   => $email,
            'user_email'   => $email,
            'user_pass'    => $password,
            'first_name'   => $billing['first_name'] ?? '',
            'last_name'    => $billing['last_name'] ?? '',
            'display_name' => trim(($billing['first_name'] ?? '') . ' ' . ($billing['last_name'] ?? '')) ?: $email,
            'role'         => 'customer', // WooCommerce customer role
          ];

          $user_id = wp_insert_user($userdata);

          if (! is_wp_error($user_id)) {
            // Set WooCommerce customer billing data
            $customer = new \WC_Customer($user_id);

            // Billing address - sanitize and validate each field
            $first_name = self::sanitize_field($billing['first_name'] ?? '');
            $last_name  = self::sanitize_field($billing['last_name'] ?? '');
            $email      = self::sanitize_email_field($billing['email'] ?? '');
            $phone      = self::sanitize_phone($billing['phone'] ?? '');
            $address_1  = self::sanitize_field($billing['address_1'] ?? '');
            $address_2  = self::sanitize_field($billing['address_2'] ?? '');
            $city       = self::sanitize_field($billing['city'] ?? '');
            $state      = self::sanitize_field($billing['state'] ?? '');
            $postcode   = self::sanitize_field($billing['postcode'] ?? '');
            $country    = self::sanitize_field($billing['country'] ?? '');

            // Only set if not empty
            if ($first_name !== '') $customer->set_billing_first_name($first_name);
            if ($last_name !== '')  $customer->set_billing_last_name($last_name);
            if ($email !== '')      $customer->set_billing_email($email);
            if ($phone !== '')      $customer->set_billing_phone($phone);
            if ($address_1 !== '')  $customer->set_billing_address_1($address_1);
            if ($address_2 !== '')  $customer->set_billing_address_2($address_2);
            if ($city !== '')       $customer->set_billing_city($city);
            if ($state !== '')      $customer->set_billing_state($state);
            if ($postcode !== '')   $customer->set_billing_postcode($postcode);
            if ($country !== '')    $customer->set_billing_country($country);

            // Shipping address (if provided)
            if (! empty($shipping) && is_array($shipping)) {
              $s_first_name = self::sanitize_field($shipping['first_name'] ?? '');
              $s_last_name  = self::sanitize_field($shipping['last_name'] ?? '');
              $s_address_1  = self::sanitize_field($shipping['address_1'] ?? '');
              $s_address_2  = self::sanitize_field($shipping['address_2'] ?? '');
              $s_city       = self::sanitize_field($shipping['city'] ?? '');
              $s_state      = self::sanitize_field($shipping['state'] ?? '');
              $s_postcode   = self::sanitize_field($shipping['postcode'] ?? '');
              $s_country    = self::sanitize_field($shipping['country'] ?? '');

              if ($s_first_name !== '') $customer->set_shipping_first_name($s_first_name);
              if ($s_last_name !== '')  $customer->set_shipping_last_name($s_last_name);
              if ($s_address_1 !== '')  $customer->set_shipping_address_1($s_address_1);
              if ($s_address_2 !== '')  $customer->set_shipping_address_2($s_address_2);
              if ($s_city !== '')       $customer->set_shipping_city($s_city);
              if ($s_state !== '')      $customer->set_shipping_state($s_state);
              if ($s_postcode !== '')   $customer->set_shipping_postcode($s_postcode);
              if ($s_country !== '')    $customer->set_shipping_country($s_country);
            }

            $customer->save();

            // Link order to customer
            $order->set_customer_id($user_id);
            $order->save();

            // Send new user notification (password reset link)
            wp_new_user_notification($user_id, null, 'user');
          }
        }
      }

      // Schedule email notification with 15s delay (non-blocking) - on shutdown to avoid blocking
      add_action('shutdown', function () use ($order, $status) {
        self::schedule_order_email($order->get_id(), $status);
      }, 999);

      return new \WP_REST_Response([
        'ok'      => true,
        'id'      => $order->get_id(),
        'number'  => $order->get_order_number(),
      ], 201);
    } catch (\Throwable $e) {
      // Log the actual error for debugging

      // Return user-friendly error message
      return new \WP_REST_Response([
        'error' => 'create_failed',
        'message' => __('Unable to create order. Please check all required fields and try again.', 'woonoow')
      ], 500);
    }
  }

  /**
   * GET /woonoow/v1/products
   */
  public static function products(WP_REST_Request $req): WP_REST_Response
  {
    $s     = sanitize_text_field($req->get_param('search') ?? '');
    $limit = max(1, min(20, absint($req->get_param('limit') ?? 10)));

    // Use WP_Query for proper search support (wc_get_products doesn't support 's' parameter)
    $args = [
      'post_type'      => ['product'],
      'post_status'    => 'publish',
      'posts_per_page' => $limit,
    ];
    if ($s) {
      $args['s'] = $s;
    }

    $query = new \WP_Query($args);
    $prods = array_filter(array_map('wc_get_product', $query->posts));
    $rows  = array_map(function ($p) {
      $data = [
        'id'           => $p->get_id(),
        'name'         => $p->get_name(),
        'type'         => $p->get_type(),
        'price'        => (float) $p->get_price(),
        'regular_price' => (float) $p->get_regular_price(),
        'sale_price'   => $p->get_sale_price() ? (float) $p->get_sale_price() : null,
        'sku'          => $p->get_sku(),
        'stock'        => $p->get_stock_quantity(),
        'virtual'      => $p->is_virtual(),
        'downloadable' => $p->is_downloadable(),
        'variations'   => [],
      ];

      // If variable product, include variations
      if ($p->get_type() === 'variable') {
        $variation_ids = $p->get_children();
        $parent_attributes = $p->get_attributes();

        foreach ($variation_ids as $variation_id) {
          $variation = wc_get_product($variation_id);
          if (! $variation) continue;

          // Get variation attributes properly from parent attributes
          $formatted_attributes = [];

          foreach ($parent_attributes as $parent_attr) {
            if (! $parent_attr->get_variation()) {
              continue; // Skip non-variation attributes
            }

            $attr_name = $parent_attr->get_name();
            $clean_name = $attr_name;

            // Get the variation's value for this attribute
            if (strpos($attr_name, 'pa_') === 0) {
              // Global/taxonomy attribute
              $clean_name = wc_attribute_label($attr_name);
              $value = $variation->get_attribute($attr_name);

              // Convert slug to term name
              if (! empty($value)) {
                $term = get_term_by('slug', $value, $attr_name);
                $value = $term ? $term->name : $value;
              }
            } else {
              // Custom attribute - WooCommerce stores as 'attribute_' + lowercase sanitized name
              $meta_key = 'attribute_' . sanitize_title($attr_name);
              $value = get_post_meta($variation_id, $meta_key, true);

              // Capitalize the attribute name for display
              $clean_name = ucfirst($attr_name);
            }

            $formatted_attributes[$clean_name] = $value;
          }

          $data['variations'][] = [
            'id'         => $variation->get_id(),
            'attributes' => $formatted_attributes,
            'price'      => (float) $variation->get_price(),
            'regular_price' => (float) $variation->get_regular_price(),
            'sale_price' => $variation->get_sale_price() ? (float) $variation->get_sale_price() : null,
            'sku'        => $variation->get_sku(),
            'stock'      => $variation->get_stock_quantity(),
            'in_stock'   => $variation->is_in_stock(),
          ];
        }
      }

      return $data;
    }, $prods);

    return new WP_REST_Response(['rows' => $rows], 200);
  }

  public static function payments(WP_REST_Request $req): WP_REST_Response
  {
    $methods = [];
    if (function_exists('WC')) {
      $gateways = WC()->payment_gateways ? WC()->payment_gateways->get_available_payment_gateways() : [];

      foreach ($gateways as $id => $gw) {
        // Skip disabled gateways (double-check even though get_available_payment_gateways should filter)
        if (! isset($gw->enabled) || $gw->enabled !== 'yes') {
          continue;
        }

        $gateway_data = [
          'id'       => (string) $id,
          'title'    => (string) ($gw->get_title() ?? $gw->title ?? $id),
          'enabled'  => true,
          'channels' => [],
        ];

        /**
         * Filter: woonoow/payment_gateway_channels
         * 
         * Allows payment gateways to provide channel-level options (e.g., bank accounts).
         * If channels are provided, they will be shown instead of the gateway itself.
         * 
         * @param array  $channels   Array of channel configurations
         * @param string $gateway_id Gateway ID
         * @param object $gateway    Gateway instance
         * 
         * Example:
         * add_filter('woonoow/payment_gateway_channels', function($channels, $gateway_id, $gateway) {
         *     if ($gateway_id === 'bacs') {
         *         $accounts = $gateway->get_option('account_details', []);
         *         foreach ($accounts as $account) {
         *             $channels[] = [
         *                 'id'    => 'bacs_' . sanitize_title($account['account_name']),
         *                 'title' => $account['bank_name'] . ' - ' . $account['account_name'],
         *                 'meta'  => $account,
         *             ];
         *         }
         *     }
         *     return $channels;
         * }, 10, 3);
         */
        $channels = apply_filters('woonoow/payment_gateway_channels', [], $id, $gw);

        // If gateway has channels, add them
        if (! empty($channels) && is_array($channels)) {
          $gateway_data['channels'] = $channels;
        }

        $methods[] = $gateway_data;
      }
    }
    return new \WP_REST_Response($methods, 200);
  }

  public static function shippings(WP_REST_Request $req): WP_REST_Response
  {
    $rows = [];
    if (! class_exists('\WC_Shipping_Zones')) {
      return new \WP_REST_Response($rows, 200);
    }

    $zones = \WC_Shipping_Zones::get_zones();
    // Also include the “locations not covered by your other zones” pseudo-zone
    $zones[] = \WC_Shipping_Zones::get_zone_by('zone_id', 0)->get_data();

    foreach ($zones as $zone) {
      $zone_obj = is_object($zone) ? $zone : new \WC_Shipping_Zone($zone['id'] ?? 0);
      foreach ($zone_obj->get_shipping_methods() as $m) {
        /** @var \WC_Shipping_Method $m */

        // Skip disabled shipping methods
        if (! $m->is_enabled()) {
          continue;
        }

        $id = $m->id;
        $instance = method_exists($m, 'get_instance_id') ? $m->get_instance_id() : 0;
        $title = !empty($m->title) ? $m->title : (method_exists($m, 'get_method_title') ? $m->get_method_title() : $m->id);

        // Get shipping cost
        $cost = 0;
        if (method_exists($m, 'get_option')) {
          $cost = $m->get_option('cost', 0);
        }

        $rows[] = [
          'id'     => $instance ? "{$id}:{$instance}" : $id, // e.g. "flat_rate:3" or "free_shipping"
          'method' => $id,
          'title'  => (string) ($m->title ?? $m->get_method_title() ?? $id), // Use user's label first
          'cost'   => (float) $cost, // Shipping cost
        ];
      }
    }
    // Deduplicate by id
    $rows = array_values(array_reduce($rows, function ($carry, $r) {
      $carry[$r['id']] = $r;
      return $carry;
    }, []));

    return new \WP_REST_Response($rows, 200);
  }

  /**
   * Calculate shipping rates for given cart and address
   * POST /woonoow/v1/shipping/calculate
   */
  public static function calculate_shipping(WP_REST_Request $req): \WP_REST_Response
  {
    $p = $req->get_json_params();
    $items = is_array($p['items'] ?? null) ? $p['items'] : [];
    $shipping = is_array($p['shipping'] ?? null) ? $p['shipping'] : [];

    if (empty($items)) {
      return new \WP_REST_Response(['error' => 'items_required'], 400);
    }

    try {
      // Initialize WooCommerce cart and session if not already
      if (! WC()->cart) {
        wc_load_cart();
      }
      if (! WC()->session) {
        WC()->session = new \WC_Session_Handler();
        WC()->session->init();
      }

      // Create a temporary cart to calculate shipping
      WC()->cart->empty_cart();

      // Add items to cart
      foreach ($items as $item) {
        $product_id = absint($item['product_id'] ?? 0);
        $qty = max(1, absint($item['qty'] ?? 1));
        if ($product_id) {
          WC()->cart->add_to_cart($product_id, $qty);
        }
      }

      // Set customer shipping address
      if (! empty($shipping)) {
        $country = $shipping['country'] ?? '';
        $state = $shipping['state'] ?? '';
        $postcode = $shipping['postcode'] ?? '';
        $city = $shipping['city'] ?? '';
        $address_1 = $shipping['address_1'] ?? '';
        $address_2 = $shipping['address_2'] ?? '';

        WC()->customer->set_shipping_country($country);
        WC()->customer->set_shipping_state($state);
        WC()->customer->set_shipping_postcode($postcode);
        WC()->customer->set_shipping_city($city);
        WC()->customer->set_shipping_address($address_1);
        WC()->customer->set_shipping_address_2($address_2);

        // Also set billing for tax calculation context
        WC()->customer->set_billing_country($country);
        WC()->customer->set_billing_state($state);
        WC()->customer->set_billing_postcode($postcode);
        WC()->customer->set_billing_city($city);

        /**
         * Allow shipping addons to prepare session/data before shipping calculation.
         * 
         * This hook allows third-party shipping plugins (like Rajaongkir, Biteship, etc.)
         * to set any session variables or prepare data they need before WooCommerce
         * calculates shipping rates.
         * 
         * @since 1.0.0
         * @param array $shipping The shipping address data from frontend (country, state, city, postcode, address_1, etc.)
         * @param array $items    The cart items being shipped
         */
        do_action('woonoow/shipping/before_calculate', $shipping, $items ?? []);
      }

      // Calculate shipping
      WC()->cart->calculate_shipping();
      WC()->cart->calculate_totals();

      // Get available shipping packages and rates
      $packages = WC()->shipping()->get_packages();
      $rates = [];

      foreach ($packages as $package_key => $package) {
        $package_rates = $package['rates'] ?? [];

        foreach ($package_rates as $rate_id => $rate) {
          /** @var \WC_Shipping_Rate $rate */
          $rates[] = [
            'id' => $rate_id,
            'method_id' => $rate->get_method_id(),
            'instance_id' => $rate->get_instance_id(),
            'label' => $rate->get_label(),
            'cost' => (float) $rate->get_cost(),
            'taxes' => $rate->get_taxes(),
            'meta_data' => $rate->get_meta_data(),
          ];
        }
      }

      // Fallback: If no rates from packages, manually calculate from matching zone
      if (empty($rates) && ! empty($shipping['country'])) {
        $package = [
          'destination' => [
            'country' => $shipping['country'] ?? '',
            'state' => $shipping['state'] ?? '',
            'postcode' => $shipping['postcode'] ?? '',
            'city' => $shipping['city'] ?? '',
          ],
          'contents' => WC()->cart->get_cart(),
          'contents_cost' => WC()->cart->get_subtotal(),
          'applied_coupons' => WC()->cart->get_applied_coupons(),
          'user' => ['ID' => get_current_user_id()],
        ];

        $zone = \WC_Shipping_Zones::get_zone_matching_package($package);
        if ($zone) {
          foreach ($zone->get_shipping_methods(true) as $method) {
            if (method_exists($method, 'get_rates_for_package')) {
              $method_rates = $method->get_rates_for_package($package);
              foreach ($method_rates as $rate_id => $rate) {
                $rates[] = [
                  'id' => $rate_id,
                  'method_id' => $rate->get_method_id(),
                  'instance_id' => $rate->get_instance_id(),
                  'label' => $rate->get_label(),
                  'cost' => (float) $rate->get_cost(),
                  'taxes' => $rate->get_taxes(),
                  'meta_data' => $rate->get_meta_data(),
                ];
              }
            } else {
              // Fallback for methods without get_rates_for_package (like Free Shipping)
              $method_id = $method->id . ':' . $method->instance_id;
              $rates[] = [
                'id' => $method_id,
                'method_id' => $method->id,
                'instance_id' => $method->instance_id,
                'label' => $method->get_title(),
                'cost' => $method->id === 'free_shipping' ? 0 : (float) ($method->cost ?? 0),
                'taxes' => [],
                'meta_data' => [],
              ];
            }
          }
        }
      }

      // Clean up
      WC()->cart->empty_cart();

      return new \WP_REST_Response([
        'methods' => $rates, // Keep as 'methods' for frontend compatibility
        'has_methods' => ! empty($rates),
        'debug' => [
          'packages_count' => count($packages),
          'cart_items_count' => count(WC()->cart->get_cart()),
          'address' => $shipping,
        ],
      ], 200);
    } catch (\Throwable $e) {
      // Clean up on error
      WC()->cart->empty_cart();

      return new \WP_REST_Response([
        'error' => 'calculation_failed',
        'message' => $e->getMessage(),
      ], 500);
    }
  }

  /**
   * Preview order totals (subtotal, shipping, tax, total)
   * POST /woonoow/v1/orders/preview
   */
  public static function preview_order(WP_REST_Request $req): \WP_REST_Response
  {
    $p = $req->get_json_params();
    $items = is_array($p['items'] ?? null) ? $p['items'] : [];
    $billing = is_array($p['billing'] ?? null) ? $p['billing'] : [];
    $shipping = is_array($p['shipping'] ?? null) ? $p['shipping'] : [];
    $shipping_method = sanitize_text_field($p['shipping_method'] ?? '');
    $coupons = array_filter(array_map('sanitize_text_field', (array) ($p['coupons'] ?? [])));

    if (empty($items)) {
      return new \WP_REST_Response(['error' => 'items_required'], 400);
    }

    try {
      // Initialize WooCommerce cart and session if not already
      if (! WC()->cart) {
        wc_load_cart();
      }
      if (! WC()->session) {
        WC()->session = new \WC_Session_Handler();
        WC()->session->init();
      }

      // Use WooCommerce cart for calculation
      WC()->cart->empty_cart();

      // Add items
      foreach ($items as $item) {
        $product_id = absint($item['product_id'] ?? 0);
        $qty = max(1, absint($item['qty'] ?? 1));
        if ($product_id) {
          WC()->cart->add_to_cart($product_id, $qty);
        }
      }

      // Set customer addresses for tax calculation
      if (! empty($billing)) {
        WC()->customer->set_billing_country($billing['country'] ?? '');
        WC()->customer->set_billing_state($billing['state'] ?? '');
        WC()->customer->set_billing_postcode($billing['postcode'] ?? '');
        WC()->customer->set_billing_city($billing['city'] ?? '');
      }

      if (! empty($shipping)) {
        WC()->customer->set_shipping_country($shipping['country'] ?? '');
        WC()->customer->set_shipping_state($shipping['state'] ?? '');
        WC()->customer->set_shipping_postcode($shipping['postcode'] ?? '');
        WC()->customer->set_shipping_city($shipping['city'] ?? '');
      }

      // Apply coupons
      foreach ($coupons as $code) {
        WC()->cart->apply_coupon($code);
      }

      // Set chosen shipping method if provided
      if ($shipping_method) {
        WC()->session->set('chosen_shipping_methods', [$shipping_method]);
      }

      // Calculate totals
      WC()->cart->calculate_shipping();
      WC()->cart->calculate_totals();

      // Get totals
      $result = [
        'subtotal' => (float) WC()->cart->get_subtotal(),
        'subtotal_tax' => (float) WC()->cart->get_subtotal_tax(),
        'discount_total' => (float) WC()->cart->get_discount_total(),
        'discount_tax' => (float) WC()->cart->get_discount_tax(),
        'shipping_total' => (float) WC()->cart->get_shipping_total(),
        'shipping_tax' => (float) WC()->cart->get_shipping_tax(),
        'cart_contents_tax' => (float) WC()->cart->get_cart_contents_tax(),
        'fee_tax' => (float) WC()->cart->get_fee_tax(),
        'total_tax' => (float) WC()->cart->get_total_tax(),
        'total' => (float) WC()->cart->get_total('edit'),
        'tax_display_cart' => get_option('woocommerce_tax_display_cart', 'excl'),
        'prices_include_tax' => wc_prices_include_tax(),
      ];

      // Clean up
      WC()->cart->empty_cart();

      return new \WP_REST_Response($result, 200);
    } catch (\Throwable $e) {
      // Clean up on error
      WC()->cart->empty_cart();

      return new \WP_REST_Response([
        'error' => 'preview_failed',
        'message' => $e->getMessage(),
      ], 500);
    }
  }

  public static function countries(WP_REST_Request $req): \WP_REST_Response
  {
    $rows    = [];
    $states  = [];
    $default = '';

    if (function_exists('wc') && wc() && isset(wc()->countries)) {
      $c = wc()->countries;

      // All countries and which ones are allowed for checkout
      $all      = (array) $c->get_countries();            // [ 'ID' => 'Indonesia', ... ]
      $allowed  = (array) $c->get_allowed_countries();    // obeys General ▶ Selling location(s)
      $states   = (array) $c->get_states();               // [ 'US' => [ 'CA' => 'California', ... ], ... ]

      // Use only the allowed countries list; if empty, fall back to all.
      $use = ! empty($allowed) ? $allowed : $all;       // shape: [ 'ID' => 'Indonesia', ... ]

      // Default country is the store base if present in the allowed set; otherwise first allowed.
      $base     = wc_get_base_location();                 // ['country' => 'ID', 'state' => 'JK']
      $default  = isset($base['country']) && array_key_exists($base['country'], $use)
        ? $base['country']
        : (string) (array_key_first($use) ?: '');

      // Filter states down to the allowed countries only
      if (! empty($states)) {
        $states = array_intersect_key($states, $use);
      }

      foreach ($use as $code => $name) {
        $rows[] = [
          'code' => (string) $code,
          'name' => (string) $name,
        ];
      }
    }

    // Shape: { countries: [{code,name}], states: { CODE: {STATE: Name} }, default_country: 'ID' }
    return new \WP_REST_Response([
      'countries'       => $rows,
      'states'          => $states,
      'default_country' => $default,
    ], 200);
  }

  /**
   * Send order email notification (runs async via Action Scheduler or wp-cron).
   * This is triggered 15 seconds after order status change to avoid blocking API responses.
   */
  public static function send_order_email($args)
  {
    $order_id = absint($args['order_id'] ?? 0);
    $status   = sanitize_text_field($args['status'] ?? '');

    if (! $order_id || ! $status) {
      return;
    }

    $order = wc_get_order($order_id);
    if (! $order) {
      return;
    }

    // Trigger WooCommerce email notification for the status
    // This will call wp_mail() which is intercepted by WooEmailOverride
    do_action('woocommerce_order_status_' . $status, $order_id, $order);

    // Also trigger generic status change hook
    do_action('woocommerce_order_status_changed', $order_id, $order->get_status(), $status, $order);
  }

  /**
   * Delete an order (move to trash or permanently delete based on WooCommerce settings)
   */
  public static function delete(WP_REST_Request $req): WP_REST_Response
  {
    $id = (int) $req['id'];

    if (! $id) {
      return new WP_REST_Response([
        'error' => __('Invalid order ID', 'woonoow'),
      ], 400);
    }

    $order = wc_get_order($id);
    if (! $order) {
      return new WP_REST_Response([
        'error' => __('Order not found', 'woonoow'),
      ], 404);
    }

    // Check if HPOS is enabled
    $using_hpos = OrderUtil::custom_orders_table_usage_is_enabled();

    try {
      // For HPOS orders, use the delete method directly
      if ($using_hpos) {
        // Move to trash first (soft delete)
        $order->delete(false);

        // If you want to permanently delete instead, use:
        // $order->delete( true );
      } else {
        // For legacy post-based orders, use wp_trash_post or wp_delete_post
        wp_trash_post($id);

        // For permanent deletion:
        // wp_delete_post( $id, true );
      }

      // Log the deletion
      if (function_exists('wc_get_logger')) {
        $logger = wc_get_logger();
        $logger->info(
          sprintf('Order #%d deleted via WooNooW API by user %d', $id, get_current_user_id()),
          ['source' => 'woonoow-orders']
        );
      }

      return new WP_REST_Response([
        'success' => true,
        'id'      => $id,
        'message' => __('Order deleted successfully', 'woonoow'),
      ], 200);
    } catch (\Exception $e) {
      return new WP_REST_Response([
        'error'   => __('Failed to delete order', 'woonoow'),
        'message' => $e->getMessage(),
      ], 500);
    }
  }

  /**
   * Get payment method title from order
   * Handles both regular gateways and channel-based payments (e.g., bacs_dwindi-ramadhana_0)
   * 
   * @param \WC_Order $order Order object
   * @return string Payment method title
   */
  private static function get_payment_method_title($order): string
  {
    $payment_method_id = $order->get_payment_method();

    if (empty($payment_method_id)) {
      return __('No payment method', 'woonoow');
    }

    // Get ALL payment gateways (not just available ones)
    if (function_exists('WC')) {
      $gateways_handler = WC()->payment_gateways();
      if ($gateways_handler) {
        $all_gateways = $gateways_handler->payment_gateways();

        // First, check if it's a direct gateway ID
        if (isset($all_gateways[$payment_method_id])) {
          $gateway = $all_gateways[$payment_method_id];
          return $gateway->get_title() ?: $gateway->method_title;
        }

        // If not a direct gateway, check if it's a channel ID (e.g., bacs_dwindi-ramadhana_0)
        if (strpos($payment_method_id, '_') !== false) {
          $parts = explode('_', $payment_method_id, 2);
          $gateway_id = $parts[0];

          if (isset($all_gateways[$gateway_id])) {
            $gateway = $all_gateways[$gateway_id];

            // Get channels for this gateway
            $channels = apply_filters('woonoow/payment_gateway_channels', [], $gateway_id, $gateway);

            // Find matching channel
            foreach ($channels as $channel) {
              if ($channel['id'] === $payment_method_id) {
                return $channel['title'];
              }
            }
          }
        }
      }
    }

    // Fallback to WooCommerce's get_payment_method_title()
    $title = $order->get_payment_method_title();
    return $title ?: __('No payment method', 'woonoow');
  }

  /**
   * Get shipping method title from order
   * Uses WC_Shipping_Zones to get the actual user-configured title
   * 
   * @param \WC_Order $order Order object
   * @return string Shipping method title
   */
  private static function get_shipping_method_title($order): string
  {
    $shipping_methods = $order->get_shipping_methods();

    if (empty($shipping_methods)) {
      return __('No shipping method', 'woonoow');
    }

    // Get first shipping method item
    $shipping_item = reset($shipping_methods);
    $method_id = $shipping_item->get_method_id();
    $instance_id = $shipping_item->get_instance_id();

    // Look up the actual shipping method from WC_Shipping_Zones
    if (class_exists('\WC_Shipping_Zones') && $instance_id) {
      $zones = \WC_Shipping_Zones::get_zones();
      $zones[] = \WC_Shipping_Zones::get_zone_by('zone_id', 0)->get_data();

      foreach ($zones as $zone) {
        $zone_obj = is_object($zone) ? $zone : new \WC_Shipping_Zone($zone['id'] ?? 0);
        foreach ($zone_obj->get_shipping_methods() as $method) {
          // Match by method_id and instance_id
          if ($method->id === $method_id && $method->get_instance_id() == $instance_id) {
            // Found it! Return the user's custom title
            return $method->title ?: $method->get_method_title() ?: $method_id;
          }
        }
      }
    }

    // Fallback: try to get title from the order item itself
    $title = $shipping_item->get_name();
    if ($title && $title !== $method_id) {
      return $title;
    }

    // Last resort: return method ID
    return $method_id ?: __('No shipping method', 'woonoow');
  }

  /**
   * Get shipping method ID from order
   * WooCommerce format: "method_id:instance_id" (e.g., "free_shipping:1")
   * 
   * @param \WC_Order $order Order object
   * @return string Shipping method ID with instance
   */
  private static function get_shipping_method_id($order): string
  {
    $shipping_methods = $order->get_shipping_methods();
    if (empty($shipping_methods)) {
      return '';
    }

    // Get first shipping method
    $shipping_method = reset($shipping_methods);
    $method_id = $shipping_method->get_method_id();
    $instance_id = $shipping_method->get_instance_id();

    // Return in WooCommerce format: method_id:instance_id
    return $instance_id ? "{$method_id}:{$instance_id}" : $method_id;
  }

  /**
   * GET /woonoow/v1/customers/search
   * Search customers by email or general search for autofill functionality
   */
  public static function search_customers(WP_REST_Request $req): WP_REST_Response
  {
    $email  = sanitize_email($req->get_param('email') ?? '');
    $search = sanitize_text_field($req->get_param('search') ?? '');

    // If email provided, search by email (exact match)
    if (! empty($email)) {
      $user = get_user_by('email', $email);

      if (! $user) {
        return new \WP_REST_Response(['found' => false], 200);
      }

      return new \WP_REST_Response(self::format_customer_data($user), 200);
    }

    // If search provided, search by name/email (multiple results)
    if (! empty($search)) {
      $users = get_users([
        'search'         => '*' . $search . '*',
        'search_columns' => ['user_login', 'user_email', 'display_name'],
        'number'         => 10,
        'role__in'       => ['customer', 'administrator', 'shop_manager', 'subscriber'],
      ]);

      $results = [];
      foreach ($users as $user) {
        $customer = new \WC_Customer($user->ID);
        $results[] = [
          'id'         => $user->ID,
          'email'      => $user->user_email,
          'name'       => trim(($customer->get_billing_first_name() ?: $user->first_name) . ' ' . ($customer->get_billing_last_name() ?: $user->last_name)),
          'first_name' => $customer->get_billing_first_name() ?: $user->first_name,
          'last_name'  => $customer->get_billing_last_name() ?: $user->last_name,
        ];
      }

      return new \WP_REST_Response($results, 200);
    }

    return new \WP_REST_Response(['error' => 'email_or_search_required'], 400);
  }

  /**
   * Format customer data for autofill
   */
  private static function format_customer_data($user): array
  {
    $customer = new \WC_Customer($user->ID);

    return [
      'found'      => true,
      'user_id'    => $user->ID,
      'email'      => $user->user_email,
      'first_name' => $customer->get_billing_first_name() ?: $user->first_name,
      'last_name'  => $customer->get_billing_last_name() ?: $user->last_name,
      'phone'      => $customer->get_billing_phone(),
      'billing'    => [
        'first_name' => $customer->get_billing_first_name(),
        'last_name'  => $customer->get_billing_last_name(),
        'email'      => $customer->get_billing_email() ?: $user->user_email,
        'phone'      => $customer->get_billing_phone(),
        'address_1'  => $customer->get_billing_address_1(),
        'address_2'  => $customer->get_billing_address_2(),
        'city'       => $customer->get_billing_city(),
        'state'      => $customer->get_billing_state(),
        'postcode'   => $customer->get_billing_postcode(),
        'country'    => $customer->get_billing_country(),
      ],
      'shipping'   => [
        'first_name' => $customer->get_shipping_first_name(),
        'last_name'  => $customer->get_shipping_last_name(),
        'address_1'  => $customer->get_shipping_address_1(),
        'address_2'  => $customer->get_shipping_address_2(),
        'city'       => $customer->get_shipping_city(),
        'state'      => $customer->get_shipping_state(),
        'postcode'   => $customer->get_shipping_postcode(),
        'country'    => $customer->get_shipping_country(),
      ],
    ];
  }

  /**
   * Process payment gateway for an order
   * Calls the gateway's process_payment() method to create external transactions
   * 
   * @param \WC_Order $order Order object
   * @param string $payment_method Payment method ID
   * @return array|\WP_Error Payment result or error
   */
  private static function process_payment_gateway($order, $payment_method)
  {
    // Get all payment gateways
    if (! function_exists('WC') || ! WC()->payment_gateways) {
      return new \WP_Error('no_wc', __('WooCommerce not initialized', 'woonoow'));
    }

    // Initialize WooCommerce cart and session if not exists
    // Some gateways call WC()->cart->empty_cart() or WC()->session->set() after payment processing
    if (! WC()->cart) {
      WC()->initialize_cart();
    }

    // Initialize session if not exists
    if (! WC()->session || ! WC()->session instanceof \WC_Session) {
      WC()->initialize_session();
    }

    $gateways = WC()->payment_gateways->payment_gateways();

    // Extract base gateway ID (handle channels like bacs_account_0)
    $gateway_id = $payment_method;
    if (strpos($payment_method, '_') !== false) {
      $parts = explode('_', $payment_method, 2);
      // Check if base gateway exists
      if (isset($gateways[$parts[0]])) {
        $gateway_id = $parts[0];
      }
    }

    // Check if gateway exists
    if (! isset($gateways[$gateway_id])) {
      return new \WP_Error('gateway_not_found', sprintf(__('Payment gateway not found: %s', 'woonoow'), $gateway_id));
    }

    $gateway = $gateways[$gateway_id];

    // Check if gateway has process_payment method
    if (! method_exists($gateway, 'process_payment')) {
      return new \WP_Error('no_process_method', sprintf(__('Gateway does not support payment processing: %s', 'woonoow'), $gateway_id));
    }

    try {
      // Mark as admin-created for gateways that need to know
      $order->update_meta_data('_woonoow_admin_created', true);
      $order->save();

      // Set flag for gateways to detect admin context
      add_filter('woonoow/is_admin_order', '__return_true');


      // Call gateway's process_payment method
      $result = $gateway->process_payment($order->get_id());

      // Clean up filter
      remove_filter('woonoow/is_admin_order', '__return_true');

      // Store result metadata
      if (is_array($result)) {
        if (isset($result['redirect'])) {
          $order->update_meta_data('_woonoow_payment_redirect', $result['redirect']);
        }

        if (isset($result['result']) && $result['result'] === 'success') {
          $order->add_order_note(__('Payment gateway processing completed via WooNooW', 'woonoow'));
        } elseif (isset($result['result']) && $result['result'] === 'failure') {
          $message = isset($result['message']) ? $result['message'] : __('Payment processing failed', 'woonoow');
          $order->add_order_note(sprintf(__('Payment gateway error: %s', 'woonoow'), $message));
        }

        $order->save();
      }

      return $result;
    } catch (\Throwable $e) {
      $order->add_order_note(sprintf(__('Payment gateway exception: %s', 'woonoow'), $e->getMessage()));
      $order->save();

      return new \WP_Error('gateway_exception', $e->getMessage());
    }
  }

  /**
   * Get payment gateway metadata for display
   * Extracts common payment metadata like VA number, QR code, expiry, etc.
   * 
   * @param \WC_Order $order Order object
   * @return array Payment metadata
   */
  private static function get_payment_metadata($order): array
  {
    $meta = [];

    // Common metadata keys used by Indonesian payment gateways
    $meta_keys = apply_filters('woonoow/payment_meta_keys', [
      // Tripay
      '_tripay_payment_pay_code'      => __('Payment Code', 'woonoow'),
      '_tripay_payment_reference'     => __('Reference', 'woonoow'),
      '_tripay_payment_expired_time'  => __('Expires At', 'woonoow'),
      '_tripay_payment_amount'        => __('Amount', 'woonoow'),
      '_tripay_payment_type'          => __('Payment Type', 'woonoow'),

      // Duitku
      '_duitku_reference'             => __('Reference', 'woonoow'),
      '_duitku_va_number'             => __('VA Number', 'woonoow'),
      '_duitku_payment_url'           => __('Payment URL', 'woonoow'),

      // Xendit
      '_xendit_invoice_id'            => __('Invoice ID', 'woonoow'),
      '_xendit_invoice_url'           => __('Invoice URL', 'woonoow'),
      '_xendit_expiry_date'           => __('Expires At', 'woonoow'),

      // WooNooW
      '_woonoow_payment_redirect'     => __('Payment URL', 'woonoow'),
      '_woonoow_admin_created'        => __('Created via Admin', 'woonoow'),
    ], $order);

    foreach ($meta_keys as $key => $label) {
      $value = $order->get_meta($key);
      if (! empty($value)) {
        // Format timestamps
        if (strpos($key, 'expired_time') !== false || strpos($key, 'expiry') !== false) {
          if (is_numeric($value)) {
            $value = date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $value);
          }
        }

        // Format amounts (currency)
        if (strpos($key, 'amount') !== false && is_numeric($value)) {
          $value = wc_price($value, ['currency' => $order->get_currency()]);
        }

        // Format booleans
        if (is_bool($value)) {
          $value = $value ? __('Yes', 'woonoow') : __('No', 'woonoow');
        }

        $meta[] = [
          'key'   => $key,
          'label' => $label,
          'value' => $value,
        ];
      }
    }

    return $meta;
  }

  /**
   * POST /woonoow/v1/orders/{id}/retry-payment
   * Retry payment gateway processing for an order
   */
  public static function retry_payment(WP_REST_Request $req): WP_REST_Response
  {
    if (! current_user_can('manage_woocommerce')) {
      return new WP_REST_Response(['error' => 'forbidden'], 403);
    }

    $id = absint($req['id']);
    if (! $id) {
      return new WP_REST_Response(['error' => 'invalid_id'], 400);
    }

    $order = wc_get_order($id);
    if (! $order) {
      return new WP_REST_Response(['error' => 'not_found'], 404);
    }

    $payment_method = $order->get_payment_method();
    if (empty($payment_method)) {
      return new WP_REST_Response([
        'error' => 'no_payment_method',
        'message' => __('Order has no payment method', 'woonoow')
      ], 400);
    }

    // Only allow retry for pending/on-hold orders
    $status = $order->get_status();
    if (! in_array($status, ['pending', 'on-hold', 'failed'])) {
      return new WP_REST_Response([
        'error' => 'invalid_status',
        'message' => sprintf(
          __('Cannot retry payment for order with status: %s', 'woonoow'),
          $status
        )
      ], 400);
    }

    // Add order note
    $order->add_order_note(__('Payment retry requested via WooNooW Admin', 'woonoow'));

    // Block WooCommerce analytics tracking during ALL save operations (prevents 30s delay)
    // This needs to wrap the entire operation because process_payment_gateway() also calls save()
    add_filter('pre_http_request', function ($preempt, $args, $url) {
      if (strpos($url, 'pixel.wp.com') !== false || strpos($url, 'stats.wp.com') !== false) {
        return new \WP_Error('http_request_blocked', 'WooCommerce analytics blocked');
      }
      return $preempt;
    }, PHP_INT_MAX, 3);

    $order->save();

    // Trigger payment processing and capture result (this also calls save() internally)
    $result = self::process_payment_gateway($order, $payment_method);

    // Remove filter after ALL operations complete
    remove_all_filters('pre_http_request');

    // Check if payment processing failed
    if (is_wp_error($result)) {
      return new WP_REST_Response([
        'error' => 'payment_failed',
        'message' => $result->get_error_message()
      ], 400);
    }

    return new WP_REST_Response([
      'success' => true,
      'message' => __('Payment processing retried', 'woonoow')
    ], 200);
  }

  /**
   * POST /woonoow/v1/coupons/validate
   * Validate a coupon code and calculate discount
   */
  public static function validate_coupon(WP_REST_Request $req): WP_REST_Response
  {
    if (! current_user_can('manage_woocommerce')) {
      return new WP_REST_Response(['error' => 'forbidden'], 403);
    }

    $code = sanitize_text_field($req['code']);
    if (empty($code)) {
      return new WP_REST_Response([
        'valid' => false,
        'error' => __('Coupon code is required', 'woonoow')
      ], 400);
    }

    // Get coupon
    $coupon = new \WC_Coupon($code);

    // Check if coupon exists
    if (! $coupon->get_id()) {
      return new WP_REST_Response([
        'valid' => false,
        'error' => __('Coupon does not exist', 'woonoow')
      ], 200);
    }

    // Check if coupon is valid (not expired, usage limits, etc.)
    $validation = $coupon->is_valid();
    if (is_wp_error($validation)) {
      return new WP_REST_Response([
        'valid' => false,
        'error' => $validation->get_error_message()
      ], 200);
    }

    // Get coupon details
    $discount_type = $coupon->get_discount_type();
    $amount = $coupon->get_amount();
    $subtotal = floatval($req['subtotal'] ?? 0);

    // Calculate discount amount
    $discount_amount = 0;
    if ($discount_type === 'percent') {
      $discount_amount = ($subtotal * $amount) / 100;
    } elseif ($discount_type === 'fixed_cart') {
      $discount_amount = min($amount, $subtotal);
    } elseif ($discount_type === 'fixed_product') {
      // For product-level discounts, we'd need item details
      $discount_amount = $amount;
    }

    // Format response
    return new WP_REST_Response([
      'valid' => true,
      'code' => $coupon->get_code(),
      'discount_type' => $discount_type,
      'amount' => $amount,
      'discount_amount' => $discount_amount,
      'description' => $coupon->get_description(),
      'individual_use' => $coupon->get_individual_use(),
      'free_shipping' => $coupon->get_free_shipping(),
      'minimum_amount' => $coupon->get_minimum_amount(),
      'maximum_amount' => $coupon->get_maximum_amount(),
      'usage_count' => $coupon->get_usage_count(),
      'usage_limit' => $coupon->get_usage_limit(),
      'expiry_date' => $coupon->get_date_expires() ? $coupon->get_date_expires()->date('Y-m-d') : null,
    ], 200);
  }

  /**
   * Get order meta data for API exposure (Level 1 compatibility)
   * Filters out internal meta unless explicitly allowed
   * 
   * @param \WC_Order $order
   * @return array
   */
  private static function get_order_meta_data($order)
  {
    $meta_data = [];

    foreach ($order->get_meta_data() as $meta) {
      $key = $meta->key;
      $value = $meta->value;

      // Skip internal WooCommerce meta (starts with _wc_)
      if (strpos($key, '_wc_') === 0) {
        continue;
      }

      // Skip WooNooW internal meta
      if (strpos($key, '_woonoow_') === 0) {
        continue;
      }

      // Public meta (no underscore) - always expose
      if (strpos($key, '_') !== 0) {
        $meta_data[$key] = $value;
        continue;
      }

      // Private meta (starts with _) - check if allowed
      // Core has ZERO defaults - plugins register via filter
      $allowed_private = apply_filters('woonoow/order_allowed_private_meta', [], $order);

      if (in_array($key, $allowed_private, true)) {
        $meta_data[$key] = $value;
      }
    }

    return $meta_data;
  }

  /**
   * Update order meta data from API (Level 1 compatibility)
   * 
   * @param \WC_Order $order
   * @param array $meta_updates
   */
  private static function update_order_meta_data($order, $meta_updates)
  {
    // Get allowed updatable meta keys
    // Core has ZERO defaults - plugins register via filter
    $allowed = apply_filters('woonoow/order_updatable_meta', [], $order);

    foreach ($meta_updates as $key => $value) {
      // Skip internal WooCommerce meta
      if (strpos($key, '_wc_') === 0) {
        continue;
      }

      // Skip WooNooW internal meta
      if (strpos($key, '_woonoow_') === 0) {
        continue;
      }

      // Public meta (no underscore) - always allow
      if (strpos($key, '_') !== 0) {
        $order->update_meta_data($key, $value);
        continue;
      }

      // Private meta - check if allowed
      if (in_array($key, $allowed, true)) {
        $order->update_meta_data($key, $value);
      }
    }
  }
}
