<?php

namespace SplitPay\Core;

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

class TransferLogger {

	/**
	 * @return int|false Insert ID on success, false on failure.
	 */
	public function logTransfer( array $data ) {
		global $wpdb;
		$table = $wpdb->prefix . SPP_TRANSFER_LOG_TABLE;

		$data = array_merge(
			[
				'integration_source' => 'woocommerce',
				'source_order_id'    => null,
				// 3.7.3 (sc-7168): stripe_mode MUST be populated. The Transfers
				// admin tab queries WHERE stripe_mode = 'test'|'live', so a row
				// with NULL/empty stripe_mode is invisible to merchants. Default
				// to the WC Stripe live/test setting if the caller didn't pass
				// one through (caller-supplied value always wins).
				'stripe_mode'        => self::resolveStripeMode(),
			],
			$data
		);

		$result = $wpdb->insert( $table, $data );

		if ( ! $result ) {
			// 3.7.3 (sc-7168 hardening): silent insert failures are how the
			// "Transfers tab is empty" symptom escaped detection through several
			// releases. Surface the failure so admins can see it in the WC
			// Status > Logs view if anything goes wrong (schema mismatch, FK
			// violation, table corruption, etc.).
			self::safe_wcs_log(
				sprintf(
					'TransferLogger insert FAILED for order_id=%s, transfer_id=%s. wpdb error: %s. Payload: %s',
					$data['wc_order_id'] ?? 'unknown',
					$data['transfer_id'] ?? 'unknown',
					isset( $wpdb->last_error ) ? $wpdb->last_error : '(no last_error)',
					wp_json_encode( $data )
				)
			);

			return false;
		}

		return $wpdb->insert_id;
	}

	/**
	 * Best-effort derivation of the current Stripe mode ('test' or 'live').
	 *
	 * Matches the lookup in
	 * BSD_Split_Pay_Stripe_Connect_Woo\Inc\Admin\BSD_Split_Pay_Stripe_Connect_Woo_Table_Transfers::get_stripe_mode()
	 * so write-time and read-time agree.
	 *
	 * Order (3.7.4 hotfix — Vanessa / Payment Plugins sites):
	 *   1. Payment Plugins for Stripe WooCommerce (woocommerce_stripe_api_settings)
	 *   2. Official WooCommerce Stripe gateway (woocommerce_stripe_settings)
	 *   3. Default live
	 *
	 * @since 3.7.3
	 */
	public static function resolveStripeMode( ?string $secretKey = null ): string {
		if ( ! function_exists( 'get_option' ) ) {
			return 'live';
		}

		// PT-fix-3.7.5 (security #7): if the caller has the actual secret
		// key that drove the Stripe API call, prefer its prefix as the
		// SOURCE OF TRUTH for mode. This is what Stripe itself uses to
		// route the API call (sk_test_ / rk_test_ → test mode), so the
		// log row is guaranteed to match the actual charge. The old
		// per-gateway-option lookup is retained ONLY as a fallback for
		// callers that legitimately can't supply the key (admin tab
		// filter dropdown, migration scripts, etc.).
		if ( $secretKey !== null && $secretKey !== '' ) {
			if ( strpos( $secretKey, 'sk_test_' ) === 0 || strpos( $secretKey, 'rk_test_' ) === 0 ) {
				return 'test';
			}
			if ( strpos( $secretKey, 'sk_live_' ) === 0 || strpos( $secretKey, 'rk_live_' ) === 0 ) {
				return 'live';
			}
			// fall through to settings-based resolution for unknown prefixes
		}

		// FluentCart: prefer its own settings if FC is the active integration
		// (resolveStripeMode is called from migrations + admin filters that
		// don't know which integration paid the order). FC's settings live
		// at fluent_cart_stripe_settings with a 'testmode'-style key.
		if ( defined( 'FLUENTCART_VERSION' ) ) {
			$fc_settings = get_option( 'fluent_cart_stripe_settings' );
			if ( is_array( $fc_settings ) ) {
				if ( isset( $fc_settings['testmode'] ) && $fc_settings['testmode'] === 'yes' ) {
					return 'test';
				}
				if ( isset( $fc_settings['mode'] ) && $fc_settings['mode'] === 'test' ) {
					return 'test';
				}
			}
		}

		$pp_settings = get_option( 'woocommerce_stripe_api_settings' );
		if ( is_array( $pp_settings ) && ! empty( $pp_settings['mode'] ) ) {
			return ( $pp_settings['mode'] === 'test' ) ? 'test' : 'live';
		}

		$settings = get_option( 'woocommerce_stripe_settings' );
		if ( is_array( $settings ) && isset( $settings['testmode'] ) && $settings['testmode'] === 'yes' ) {
			return 'test';
		}

		return 'live';
	}

	/**
	 * Mirror of the safe_wcs_log() pattern documented in
	 * WooCommerceIntegration::safe_wcs_log() — see that method for the full
	 * Sand Dragon fatal regression context (Lidia / sc-7167). Inlined so the
	 * Core layer doesn't depend on any one Integration.
	 *
	 * @since 3.7.3
	 */
	private static function safe_wcs_log( string $message ): void {
		// Extensibility (3.7.5): the effective severity/verbosity this logger
		// writes at. Default is 'info', which preserves the historical chain
		// EXACTLY — WC_Stripe_Logger::log() (an info-level entry) with the
		// ::debug() and error_log() fallbacks — so with no callback attached
		// the output is byte-identical to today. A callback may return any other
		// WC_Stripe_Logger severity method name (e.g. 'debug', 'warning',
		// 'error', 'notice') to raise or lower the level the message is recorded
		// at; an unrecognised value harmlessly falls back to the default chain.
		$level = 'info';
		if ( function_exists( 'apply_filters' ) ) {
			$level = (string) apply_filters( 'spp_log_level', $level );
		}

		if ( class_exists( '\WC_Stripe_Logger' ) ) {
			// Non-default level explicitly requested: route to that severity
			// method when WC's logger exposes it. ('info'/'log' keep the
			// canonical default chain below — unchanged behaviour.)
			if ( $level !== 'info' && $level !== 'log'
				&& $level !== '' && method_exists( '\WC_Stripe_Logger', $level ) ) {
				\WC_Stripe_Logger::{$level}( $message );
				return;
			}
			if ( method_exists( '\WC_Stripe_Logger', 'log' ) ) {
				\WC_Stripe_Logger::log( $message );
				return;
			}
			if ( method_exists( '\WC_Stripe_Logger', 'debug' ) ) {
				\WC_Stripe_Logger::debug( $message );
				return;
			}
		}
		if ( function_exists( 'error_log' ) ) {
			error_log( $message );
		}
	}

	public function getTransfersByOrderId( int $orderId, ?string $source = null ): array {
		global $wpdb;
		$table = $wpdb->prefix . SPP_TRANSFER_LOG_TABLE;

		if ( $source !== null ) {
			return $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM {$table} WHERE wc_order_id = %d AND integration_source = %s",
					$orderId,
					$source
				),
				ARRAY_A
			) ?: [];
		}

		return $wpdb->get_results(
			$wpdb->prepare( "SELECT * FROM {$table} WHERE wc_order_id = %d", $orderId ),
			ARRAY_A
		) ?: [];
	}

	public function getTransfersByChargeId( string $chargeId ): array {
		global $wpdb;
		$table = $wpdb->prefix . SPP_TRANSFER_LOG_TABLE;

		return $wpdb->get_results(
			$wpdb->prepare( "SELECT * FROM {$table} WHERE charge_id = %s", $chargeId ),
			ARRAY_A
		) ?: [];
	}
}
