<?php

namespace Simplyin\Simplyin_W_Plugin\Lib\Remote\Sin_Core_Api;

use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Query;
use Simplyin\Simplyin_W_Plugin\Helper\Log_Sanitizer;
use Simplyin\Simplyin_W_Plugin\Tracking_Numbers;
use WC_Order;

class Simplyin_Api_Legacy_Client {

	protected $simplyin_api_url;
	protected $merchand_api_key;
	protected $version;
	protected $plugin_name;
	protected $saveLogs = false;
	protected $logs = [];
	private $client;

	public function __construct( $plugin_name = '', $version = '' ) {
		$this->plugin_name = $plugin_name;
		$this->version     = $version;

		$this->merchand_api_key = get_option( 'simplyin_api_key' );
		global $simplyin_config;
		// Normalize base URL once on construct to avoid trailing slashes issues
		$this->simplyin_api_url = rtrim( (string) $simplyin_config['backendSimplyIn'],
			'/' );
		if ( in_array( $simplyin_config['environmentVersion'],
			[ "dev", "pp", "st" ] ) ) {
			$this->saveLogs = true;
		}
		$this->client = new Client( [
			'timeout'         => 15,
			'connect_timeout' => 5,
		] );
	}

	public function get_available_payment_methods(): array {

		//$raw = ( new Sample_Response() )->get_sample_response();

		$raw = $this->sendRequest(
			"payments/getAvailablePaymentMethods",
			'GET'
		);


		return $raw;


	}

	public function sendRequest(
		$endpoint,
		$method = 'GET',
		?array $bodyData = null,
		$params = [],
		$merchantApiKey = "",
		$skipMerchantApiKey = false,
		$rsa_retry = false
	) {

		if ( ! empty( $merchantApiKey ) ) {
			$this->merchand_api_key = $merchantApiKey;
		}

		$rsa_signer = new Plugin_Rsa_Signer();
		$use_rsa    = ! $skipMerchantApiKey && $rsa_signer->is_active();

		// The legacy API key is only required when neither RSA signing nor
		// skipMerchantApiKey applies. A shop that has migrated to RSA may have
		// dropped its legacy key entirely — RSA auth is sufficient on its own.
		if ( empty( $this->merchand_api_key ) && ! $skipMerchantApiKey && ! $use_rsa ) {
			simplyin()
				->get_simplyin_logger()
				->log_if_wpdebug_on( sprintf( "[SimplyIn_Api] [Bad request. merchand_api_key is empty] %s",
					wp_json_encode( [
						'Endpoint'           => $endpoint,
						'Method'             => $method,
						'BodyData'           => $bodyData,
						'Params'             => $params,
						'skipMerchantApiKey' => $skipMerchantApiKey ? 'yes' : 'no',
					] )

				),
					"api-call" );

			http_response_code( 400 );  // Bad Request

			// Return a decoded object (like every other return path), not a raw JSON string —
			// callers do $response->... / isset($response->prop).
			return json_decode( '{"status":"failed","message":"Error: Simplyin API Key is empty"}' );
		}

		$headers = [
			'Content-Type' => 'application/json',
			'Origin'       => home_url(),
		];

		// Legacy API key auth — used unless RSA-signed mode is active (the two
		// are mutually exclusive: backend accepts all four RSA headers or none).
		// The backend reads the key exclusively from this header (never from the JSON
		// body), so it is not duplicated into $bodyData.
		if ( ! $skipMerchantApiKey && ! $use_rsa ) {
			$headers['X-Auth-Merchant-Api-Key'] = $this->merchand_api_key;
		}

		// RSA mode embeds the query in the URL and signs a pre-serialized body
		// so the canonical string matches the bytes actually transmitted.
		$raw_body   = '';
		$rsa_signed = false;

		if ( $use_rsa ) {
			// Encode the query with Guzzle's encoder (RFC 3986) — the exact bytes Guzzle
			// transmits — instead of http_build_query() (RFC 1738, space as '+'). This keeps
			// the signed canonical string byte-identical to the wire and consistent with
			// Plugin_Rsa_Guzzle_Client, which signs the live PSR-7 URI.
			$query = Query::build( $params );
			$url   = '' !== $query
				? $this->build_url( $endpoint ) . '?' . $query
				: $this->build_url( $endpoint );

			if ( in_array( strtoupper( $method ),
					[ "POST", "PUT", "PATCH" ] ) && $bodyData !== null ) {
				$encoded_body = wp_json_encode( $bodyData );
				if ( false === $encoded_body ) {
					// Encoding failed — do NOT sign/send an empty body while advertising JSON,
					// that would silently drop the payload. Fail fast.
					simplyin()
						->get_simplyin_logger()
						->log_if_wpdebug_on(
							sprintf( '[SimplyIn_Api] [sendRequest] Failed to JSON-encode body for %s — aborting signed request.',
								$endpoint ),
							'api-call' );

					return null;
				}
				$raw_body = $encoded_body;
			}
			// wp_parse_url() returns false on a malformed base URL; guard to an array so the
			// offset reads below don't warn and can't build a wrong (empty) path.
			$parts           = wp_parse_url( $url );
			$parts           = is_array( $parts ) ? $parts : [];
			$path_with_query = ( $parts['path'] ?? '' )
				. ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' );

			// Only sign when we resolved a real path AND the signer produced all four headers.
			// A malformed URL (empty path) or a transient signing failure would otherwise sign a
			// known-bad canonical string, guaranteeing 401s that could spuriously clear RSA
			// state via the response guard below.
			if ( '' !== $path_with_query ) {
				$rsa_headers = $rsa_signer->build_headers( $method, $path_with_query, $raw_body );
				if ( ! empty( $rsa_headers ) ) {
					$headers    = array_merge( $headers, $rsa_headers );
					$rsa_signed = true;
				}
			}
		} else {
			$url = $this->build_url( $endpoint );
		}

		simplyin()
			->get_simplyin_logger()
			->log_if_wpdebug_on( "Endpoint: " . $this->build_url( $endpoint,
					$params ),
				"api-call" );

		$options = [
			'headers'     => $headers,
			'http_errors' => false, // To handle errors manually
		];

		if ( $use_rsa ) {
			if ( '' !== $raw_body ) {
				$options['body'] = $raw_body;
			}
		} else {
			$options['query'] = $params;
			if ( in_array( strtoupper( $method ),
					[ "POST", "PUT", "PATCH" ] ) && $bodyData !== null ) {
				$options['json'] = $bodyData;
			}
		}

		$responseBody         = null;
		$error                = '';
		$exception            = '';
		$response_status_code = '';
		try {
			$response     = $this->client->request( strtoupper( $method ),
				$url,
				$options );
			$responseBody = $response->getBody()->getContents();
			$response->getStatusCode();
			$response_status_code = $response->getStatusCode();
		} catch ( RequestException $e ) {
			$error = $e->getMessage();
			if ( $e->hasResponse() ) {
				$responseBody = $e->getResponse()->getBody()->getContents();
			}
			$exception = $e;
		}

		// Decode the response for logging so Log_Sanitizer::mask() (which redacts by array key)
		// can strip sensitive fields inside it — e.g. paymentWebhookSigningSecret in the
		// plugin/sync response. As a raw string the secret would slip through unmasked.
		$response_decoded  = json_decode( (string) $responseBody, true );
		$response_for_log  = null !== $response_decoded ? $response_decoded : $responseBody;

		simplyin()
			->get_simplyin_logger()
			->log_if_wpdebug_on( sprintf( "[SimplyIn_Api] [sendRequest] %s",
				wp_json_encode( Log_Sanitizer::mask( [
					'Endpoint'             => $endpoint,
					'Method'               => $method,
					'API url'              => $this->build_url( $endpoint,
						$params ),
					'Headers'              => $headers,
					'BodyData'             => $bodyData,
					'skipMerchantApiKey'   => $skipMerchantApiKey ? 'yes' : 'no',
					'Response'             => $response_for_log,
					'error'                => $error,
					'RequestException'     => wp_json_encode( $exception ),
					'response_status_code' => $response_status_code,
				] ) )

			),
				"api-call" );

		// Rotate/revoke resilience: if this was an RSA-signed request, let
		// the signer react to the status — a 412 (RSA key not registered, i.e. post revoke/rotate)
		// or repeated 401s clear local RSA — then retry once over the legacy API key so the call
		// still succeeds during the dual-auth window. $rsa_retry guards against recursion.
		if ( $rsa_signed && ! $rsa_retry && $rsa_signer->note_rsa_response( (int) $response_status_code ) ) {
			// RSA was cleared. Retry over the legacy API key only if one is configured; a shop
			// that dropped its legacy key can't fall back — return the original response and let
			// the scheduler re-register.
			if ( ! empty( $this->merchand_api_key ) ) {
				return $this->sendRequest( $endpoint, $method, $bodyData, $params, $merchantApiKey, $skipMerchantApiKey, true );
			}
		}

		if ( null === $responseBody ) {
			simplyin()
				->get_simplyin_logger()
				->log_if_wpdebug_on(
					sprintf(
						'[SimplyIn_Api] Network error — no response body. Endpoint: %s, Error: %s',
						$endpoint,
						$error ?? 'unknown'
					),
					'api-client'
				);
			return null;
		}

		return json_decode( $responseBody );
	}

	private function log( $message ) {
		$logs   = get_option( "SimplyIn_Api_Logs", [] );
		$logs[] = $message;
		update_option( "SimplyIn_Api_Logs", $logs );
	}


	public function sendEncryptedData( $encrypted_data ) {
		//todo to remove

		global $simplyin_config;
		update_option( 'SimplyIn_Backend_Url',
			$simplyin_config['backendSimplyIn'] );

		$url = $this->build_url( 'encryption/saveEncryptedOrderStatusChange' );

		$headers = [
			'Content-Type' => 'application/json',
			'Origin'       => home_url(),
		];

		try {
			$this->client->post( $url, [
				'headers' => $headers,
				'body'    => $encrypted_data,
			] );
		} catch ( RequestException $e ) {
			// Log error if needed
			simplyin()
				->get_simplyin_logger()
				->log_if_wpdebug_on( sprintf( "[SimplyIn_Api] [sendEncryptedData] [Exception] %s",
					wp_json_encode( $e->getMessage() )
				),
					"api-call" );
		}
	}

	/**
	 * Build full request URL from base and endpoint (and optional params),
	 * cleaning potential double slashes. Normalization lives only here.
	 *
	 * @param string $endpoint
	 * @param array $params
	 *
	 * @return string
	 */
	private function build_url( string $endpoint, array $params = [] ): string {
		$base = rtrim( $this->simplyin_api_url, '/' );
		$path = ltrim( $endpoint, '/' );
		$url  = $base . '/' . $path;

		if ( ! empty( $params ) ) {
			$url .= '?' . http_build_query( $params );
		}

		return $url;
	}

	public function simplyInOnOrderUpdate(
		$old_status,
		$new_status,
		WC_Order $order
	): void {
		$stopStatuses = [
			"processing",
			"pending",
			"on-hold",
			"",
			"cancelled",
			"refunded",
			"failed",
			"checkout-draft",
		];

		$_order_id = $order->get_id(); // Assuming $order is an instance of WC_Order

		simplyin()
			->get_simplyin_logger()
			->log_if_wpdebug_on( sprintf( "[SimplyIn_Api] [simplyInOnOrderUpdate] %s",
				wp_json_encode( [
					'old status' => $old_status,
					'new status' => $new_status,
					'order_id'   => $_order_id,

				] )

			),
				"api-call" );


		$apiKey = get_option( 'simplyin_api_key' );

		$order_data  = $order->get_data();
		$order_email = $order_data['billing']['email'];

		if ( empty( $order_email ) || in_array( $new_status,
				$stopStatuses,
				true ) ) {

			simplyin()
				->get_simplyin_logger()
				->log_if_wpdebug_on( sprintf( "[SimplyIn_Api] [simplyInOnOrderUpdate] [Stop status detected] %s",
					wp_json_encode( [
						'old status' => $old_status,
						'new status' => $new_status,
						'order_id'   => $_order_id,
					] )

				),
					"api-call" );

			return;
		}

		$tracking_numbers = ( new Tracking_Numbers() )->find_tracking_numbers( $order );

		//todo - to DTO
		$body_data = [
			"email"           => $order_email,
			"shopOrderNumber" => $order->get_order_number(),
			"newOrderStatus"  => $new_status,
			"apiKey"          => $apiKey,
		];

		$simplyin_order_id = trim( (string) $order->get_meta( 'SimplyInOrderId' ) );
		if ( '' !== $simplyin_order_id ) {
			$body_data['orderId'] = $simplyin_order_id;
		}

		if ( ! empty( $tracking_numbers ) ) {
			$body_data['trackings'] = $tracking_numbers;
		}


		simplyin()
			->get_simplyin_logger()
			->log_if_wpdebug_on( sprintf( "[SimplyIn_Api] [simplyInOnOrderUpdate] [encrypted body data] %s",
				wp_json_encode( Log_Sanitizer::mask( $body_data ) )

			),
				"api-call" );

		$plaintext = json_encode( $body_data );

		$key = simplyinGetSecretKey( $order_email );

		$encryptedData = simplyinEncrypt( $plaintext, $key );

		$hashedEmail = simplyinHashEmail( $order_email );

		$orderData = [
			"encryptedOrderStatusChangeContent" => $encryptedData,
			"hashedEmail"                       => $hashedEmail,
		];

		//$this->sendEncryptedData( json_encode( $orderData ) );

		try {
			// This call goes through the same dual-auth path (RSA when active, legacy key
			// otherwise) as every other endpoint, so the backend can record a
			// rotation-proof merchant origin instead of always seeing an unauthenticated
			// request.
			$this->sendRequest( 'encryption/saveEncryptedOrderStatusChange',
				'POST',
				$orderData,
				[],
				'',
				false
			);
		} catch ( Exception $exception ) {
			simplyin()
				->get_simplyin_logger()
				->log_if_wpdebug_on( sprintf( "[SimplyIn_Api] [simplyInOnOrderUpdate] [Exception] %s",
					wp_json_encode( $exception->getMessage() )

				),
					"api-call" );

		}

	}
}
