<?php
if (!defined('ABSPATH')) exit;

class SPG_UTXO_Poller {

  const ACTION_SINGLE = 'spg_utxo_check_order';
  const ACTION_RECUR  = 'spg_utxo_poll_pending';

  // Confirmations
  const CONF_BTC  = 1;
  const CONF_LTC  = 2;
  const CONF_BCH  = 1;
  const CONF_DOGE = 6;
  const CONF_DASH = 2;
  const CONF_DGB  = 2;
  const CONF_MAZA = 2;
  const CONF_XVG  = 2;

  public static function init() {
    add_action(self::ACTION_SINGLE, [__CLASS__, 'check_order'], 10, 1);
    add_action(self::ACTION_RECUR,  [__CLASS__, 'poll_pending_orders']);
    add_action('spg_utxo_cron_poll', [__CLASS__, 'poll_pending_orders']);

    add_filter('cron_schedules', function ($schedules) {
      if (!isset($schedules['minute'])) {
        $schedules['minute'] = ['interval' => 60, 'display' => 'Every Minute'];
      }
      return $schedules;
    });
  }

  /**
   * Read gateway setting: order status after payment confirmation.
   * Allowed: processing|completed. Fallback: processing
   */
  private static function get_paid_status_from_gateway(): string {
    $status = 'processing';

    if (function_exists('WC') && WC() && WC()->payment_gateways()) {
      $gws = WC()->payment_gateways()->get_payment_gateways();
      $gw  = $gws['spg_crypto_manual'] ?? null;

      if ($gw && method_exists($gw, 'get_option')) {
        $candidate = (string) $gw->get_option('paid_order_status', 'processing');
        if (in_array($candidate, ['processing', 'completed'], true)) {
          $status = $candidate;
        }
      }
    }

    return $status;
  }

  /**
   * Apply admin-chosen paid status (processing/completed)
   * after payment_complete() is called.
   */
  private static function apply_paid_status(WC_Order $order, string $note = ''): void {
    $status = self::get_paid_status_from_gateway();

    // If already in a paid state, don't fight Woo.
    if ($order->is_paid() && in_array($order->get_status(), ['processing', 'completed'], true)) {
      return;
    }

    $order->update_status(
      $status,
      $note !== '' ? $note : __('Crypto payment confirmed.', 'spg-crypto-manual')
    );
  }

  /* ---------------- main ---------------- */

  public static function poll_pending_orders() {
    if (!class_exists('WooCommerce')) return;

    $orders = wc_get_orders([
      'limit'          => 30,
      'status'         => ['on-hold', 'pending'],
      'payment_method' => 'spg_crypto_manual',
      'date_created'   => '>' . (time() - 6 * HOUR_IN_SECONDS),
      'return'         => 'objects',
    ]);

    foreach ($orders as $order) {
      self::check_order($order->get_id());
    }
  }

  public static function check_order($order_id) {
    $order = wc_get_order((int)$order_id);
    if (!$order) return;

    $order->update_meta_data('_spg_utxo_last_checked', (string) time());
    $order->save();

    if ($order->is_paid()) return;

    // NOTE: you store coin in _spg_utxo_coin for UTXO poller logic
    $coin = strtoupper((string)$order->get_meta('_spg_utxo_coin'));
    if (!in_array($coin, ['BTC','LTC','BCH','DOGE','DASH','DGB','MAZA','XVG'], true)) return;

    $addr = trim((string)$order->get_meta('_spg_wallet_address'));
    if ($addr === '') return;

    $expected = (int)$order->get_meta('_spg_utxo_expected_sats');
    $min_acc  = (int)$order->get_meta('_spg_utxo_min_accept_sats');
    if ($expected <= 0 || $min_acc < 0) return;

    if ((string)$order->get_meta('_spg_utxo_txid') !== '') return;

    $created = $order->get_date_created();
    $min_ts = ($created ? $created->getTimestamp() : time()) - 120;

    $min_conf = self::confirmations_required($coin);

    $tx = self::find_matching_received_tx($coin, $addr, $min_ts, $min_acc, $min_conf);
    if (!$tx) return;

    $order->update_meta_data('_spg_utxo_txid', $tx['txid']);
    $order->update_meta_data('_spg_utxo_received_sats', (string)$tx['sats']);
    $order->update_meta_data('_spg_payment_status', 'paid');
    $order->save();

    $order->payment_complete($tx['txid']);

    $order->add_order_note(sprintf(
      /* translators: 1: coin, 2: txid */
      __('✅ %1$s payment detected. TXID: %2$s', 'spg-crypto-manual'),
      $coin,
      $tx['txid']
    ));

    self::apply_paid_status(
      $order,
      sprintf(
        /* translators: 1: coin */
        __('%1$s payment confirmed.', 'spg-crypto-manual'),
        $coin
      )
    );
  }

  /* ---------------- routers ---------------- */

  private static function find_matching_received_tx($coin, $addr, $min_ts, $min_acc, $min_conf) {
    $coin = strtoupper((string)$coin);

    if ($coin === 'MAZA') {
      return self::find_maza($addr, $min_ts, $min_acc, $min_conf);
    }

    if ($coin === 'XVG') {
      return self::find_xvg($addr, $min_ts, $min_acc, $min_conf);
    }

    return self::find_chainso($coin, $addr, $min_ts, $min_acc, $min_conf);
  }

  /* ---------------- confirmations + mappings (PHP 7.4 safe) ---------------- */

  private static function confirmations_required($coin) {
    switch (strtoupper((string)$coin)) {
      case 'BTC':  return self::CONF_BTC;
      case 'LTC':  return self::CONF_LTC;
      case 'BCH':  return self::CONF_BCH;
      case 'DOGE': return self::CONF_DOGE;
      case 'DASH': return self::CONF_DASH;
      case 'DGB':  return self::CONF_DGB;
      case 'MAZA': return self::CONF_MAZA;
      case 'XVG':  return self::CONF_XVG;
      default:     return 1;
    }
  }

  private static function chainso_network($coin) {
    switch (strtoupper((string)$coin)) {
      case 'BTC':  return 'BTC';
      case 'LTC':  return 'LTC';
      case 'BCH':  return 'BTCCASH';
      case 'DOGE': return 'DOGE';
      case 'DASH': return 'DASH';
      case 'DGB':  return 'DGB';
      default:     return '';
    }
  }

  /* ---------------- chain.so ---------------- */

  private static function chainso_url($net, $addr) {
    if (class_exists('SPG_API') && method_exists('SPG_API', 'chainso_get_tx_received_url')) {
      return SPG_API::chainso_get_tx_received_url($net, $addr);
    }
    return 'https://chain.so/api/v2/get_tx_received/' . rawurlencode($net) . '/' . rawurlencode($addr);
  }

  private static function find_chainso($coin, $addr, $min_ts, $min_acc, $min_conf) {
    $net = self::chainso_network($coin);
    if ($net === '') return null;

    $url = self::chainso_url($net, $addr);

    $res = wp_remote_get($url, ['timeout' => 15]);
    if (is_wp_error($res)) return null;

    $json = json_decode((string) wp_remote_retrieve_body($res), true);
    if (!is_array($json) || empty($json['data']['txs']) || !is_array($json['data']['txs'])) return null;

    foreach ($json['data']['txs'] as $tx) {
      $conf = (int)($tx['confirmations'] ?? 0);
      if ($conf < (int)$min_conf) continue;

      $ts = (int)($tx['time'] ?? 0);
      if ($ts < (int)$min_ts) continue;

      $sats = self::coin_str_to_sats($tx['value'] ?? '0');
      if ($sats >= (int)$min_acc) {
        $txid = (string)($tx['txid'] ?? '');
        if ($txid === '') continue;
        return ['txid' => $txid, 'sats' => $sats];
      }
    }

    return null;
  }

  /* ---------------- MAZA ---------------- */

  private static function maza_base() {
    if (class_exists('SPG_API') && method_exists('SPG_API', 'maza_explorer_base')) {
      $b = trim((string) SPG_API::maza_explorer_base());
      if ($b !== '') return rtrim($b, '/');
    }
    return 'https://mazacha.in';
  }

  private static function find_maza($addr, $min_ts, $min_acc, $min_conf) {
    $base = self::maza_base();

    $res = wp_remote_get($base . '/ext/getaddress/' . rawurlencode($addr), ['timeout' => 15]);
    if (is_wp_error($res)) return null;

    $json = json_decode((string) wp_remote_retrieve_body($res), true);
    if (!is_array($json) || empty($json['last_txs']) || !is_array($json['last_txs'])) return null;

    foreach ($json['last_txs'] as $row) {
      // NOTE: leaving your original field usage as-is
      $txid = isset($row['addresses']) ? (string)$row['addresses'] : '';
      if ($txid === '') continue;

      $txres = wp_remote_get($base . '/ext/gettx/' . rawurlencode($txid), ['timeout' => 15]);
      if (is_wp_error($txres)) continue;

      $txj = json_decode((string) wp_remote_retrieve_body($txres), true);
      if (!is_array($txj)) continue;

      $conf = (int)($txj['confirmations'] ?? 0);
      if ($conf < (int)$min_conf) continue;

      $ts = (int)($txj['tx']['timestamp'] ?? 0);
      if ($ts < (int)$min_ts) continue;

      $vouts = $txj['tx']['vout'] ?? [];
      if (!is_array($vouts)) continue;

      foreach ($vouts as $v) {
        if (!is_array($v)) continue;
        if ((string)($v['addresses'] ?? '') !== $addr) continue;

        $amt = (int)($v['amount'] ?? 0);
        if ($amt >= (int)$min_acc) {
          return ['txid' => $txid, 'sats' => $amt];
        }
      }
    }

    return null;
  }

  /* ---------------- XVG ---------------- */

  private static function xvg_base() {
    if (class_exists('SPG_API') && method_exists('SPG_API', 'xvg_blockbook_base')) {
      $b = trim((string) SPG_API::xvg_blockbook_base());
      if ($b !== '') return rtrim($b, '/');
    }
    return 'https://xvg-blockbook.nownodes.io';
  }

  private static function xvg_headers() {
    if (class_exists('SPG_API') && method_exists('SPG_API', 'xvg_blockbook_headers')) {
      return (array) SPG_API::xvg_blockbook_headers();
    }
    return [];
  }

  private static function find_xvg($addr, $min_ts, $min_acc, $min_conf) {
    $headers = self::xvg_headers();
    // if no key -> don't poll
    if (empty($headers['api-key']) && empty($headers['X-API-Key'])) return null;

    $base = self::xvg_base();
    $url  = $base . '/api/v2/address/' . rawurlencode($addr) . '?details=txs&pageSize=50';

    $res = wp_remote_get($url, [
      'timeout' => 20,
      'headers' => $headers,
    ]);
    if (is_wp_error($res)) return null;

    $json = json_decode((string) wp_remote_retrieve_body($res), true);
    if (!is_array($json) || empty($json['txs']) || !is_array($json['txs'])) return null;

    foreach ($json['txs'] as $tx) {
      if (!is_array($tx)) continue;

      $conf = (int)($tx['confirmations'] ?? 0);
      if ($conf < (int)$min_conf) continue;

      $ts = (int)($tx['blockTime'] ?? 0);
      if ($ts < (int)$min_ts) continue;

      $vout = $tx['vout'] ?? [];
      if (!is_array($vout)) continue;

      foreach ($vout as $o) {
        if (!is_array($o)) continue;

        $addrs = $o['addresses'] ?? [];
        if (is_string($addrs)) $addrs = [$addrs];
        if (!is_array($addrs)) $addrs = [];

        if (!in_array($addr, $addrs, true)) continue;

        if (isset($o['valueSat'])) {
          $sats = (int) preg_replace('/\D+/', '', (string)$o['valueSat']);
        } else {
          $sats = self::coin_str_to_sats($o['value'] ?? '0');
        }

        if ($sats >= (int)$min_acc) {
          $txid = (string)($tx['txid'] ?? ($tx['hash'] ?? ''));
          if ($txid === '') continue;
          return ['txid' => $txid, 'sats' => $sats];
        }
      }
    }

    return null;
  }

  /* ---------------- utils ---------------- */

  private static function coin_str_to_sats($str) {
    $str = preg_replace('/[^0-9.]/', '', (string)$str);
    if ($str === '' || $str === '.') return 0;

    $parts = explode('.', $str, 2);
    $i = preg_replace('/\D+/', '', $parts[0] ?? '0');
    $d = preg_replace('/\D+/', '', $parts[1] ?? '');
    $d = substr($d . '00000000', 0, 8);

    return ((int)$i * 100000000) + (int)$d;
  }
}

SPG_UTXO_Poller::init();