<?php

namespace WPCompanion\ScotiaBank;

if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly.

/**
 * Scotiabank Fiserv REST API client
 *
 * Handles communication with Fiserv REST gateway for secondary transactions
 * (refunds, captures, voids).
 *
 * @class   Scotiabank_Fiserv_API
 */
class Scotiabank_Fiserv_API {

	/**
	 * Fiserv API base URL — sandbox (certification) environment
	 */
	const API_BASE_URL_SANDBOX = 'https://cert.api.firstdata.com/gateway/v2';

	/**
	 * Fiserv API base URL — live (production) environment
	 */
	const API_BASE_URL_LIVE = 'https://prod.api.firstdata.com/gateway/v2';

	/**
	 * API key for authentication
	 *
	 * @var string
	 */
	private $api_key = '';

	/**
	 * API secret for signature generation
	 *
	 * @var string
	 */
	private $api_secret = '';

	/**
	 * Store ID / Name
	 *
	 * @var string
	 */
	private $store_id = '';

	/**
	 * Logger instance
	 *
	 * @var \WC_Logger
	 */
	private $log = null;

	/**
	 * Whether to use the sandbox (certification) environment
	 *
	 * @var bool
	 */
	private $sandbox = true;

	/**
	 * Constructor
	 *
	 * @param string $api_key     Fiserv API key
	 * @param string $api_secret  Fiserv API secret
	 * @param string $store_id    Store Name / ID
	 * @param object $logger      WC_Logger instance
	 * @param bool   $sandbox     True to use certification endpoint, false for production
	 */
	public function __construct( $api_key = '', $api_secret = '', $store_id = '', $logger = null, $sandbox = true ) {
		$this->api_key    = $api_key;
		$this->api_secret = $api_secret;
		$this->store_id   = $store_id;
		$this->log        = $logger ? $logger : wc_get_logger();
		$this->sandbox    = $sandbox;
	}

	/**
	 * Generate HMAC SHA256 signature for API request
	 *
	 * Signature format (from Fiserv API):
	 * string = api_key + client_request_id + timestamp + body
	 * signature = HMAC-SHA256(string, api_secret)
	 * result = Base64(signature)
	 *
	 * @param string $body Request body (JSON)
	 * @param string $client_request_id UUID for the request
	 * @param string $timestamp Request timestamp in milliseconds
	 * @return string Base64-encoded signature
	 */
	private function generate_signature( $body, $client_request_id = '', $timestamp = '' ) {
		// Construct the string to be signed: api_key + client_request_id + timestamp + body
		$string_to_sign = $this->api_key . $client_request_id . $timestamp . $body;

		// HMAC-SHA256 hash
		$hash = hash_hmac( 'sha256', $string_to_sign, $this->api_secret, true );

		// Base64 encode
		$signature = base64_encode( $hash );

		return $signature;
	}

	/**
	 * Generate a unique client request ID
	 *
	 * @return string UUID-like request ID
	 */
	private function generate_request_id() {
		return sprintf(
			'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
			mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
			mt_rand( 0, 0xffff ),
			mt_rand( 0, 0x0fff ) | 0x4000,
			mt_rand( 0, 0x3fff ) | 0x8000,
			mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
		);
	}

	/**
	 * Get current timestamp in milliseconds
	 *
	 * @return int Epoch timestamp in milliseconds
	 */
	private function get_timestamp() {
		return (int) ( microtime( true ) * 1000 );
	}

	/**
	 * Build request headers for API call
	 *
	 * @param string $body Request body (JSON)
	 * @return array Headers array
	 */
	private function build_headers( $body ) {
		$timestamp  = $this->get_timestamp();
		$request_id = $this->generate_request_id();
		$signature  = $this->generate_signature( $body, $request_id, $timestamp );

		return array(
			'Content-Type'      => 'application/json',
			'Api-Key'           => $this->api_key,
			'Client-Request-Id' => $request_id,
			'Timestamp'         => $timestamp,
			'Message-Signature' => $signature,
		);
	}

	/**
	 * Perform a secondary transaction (void, capture, refund)
	 *
	 * @param string $transaction_id Gateway transaction ID from primary transaction
	 * @param array  $payload        Request payload array
	 * @return array|WP_Error Response array or WP_Error on failure
	 */
	public function post_secondary_transaction( $transaction_id, $payload = array() ) {
		if ( empty( $transaction_id ) ) {
			$error_msg = 'Transaction ID is required for secondary transaction.';
			$this->log->add( 'ig_scotiabank', 'Fiserv API Error: ' . $error_msg );
			return new \WP_Error( 'missing_transaction_id', $error_msg );
		}

		// Sanitize and prepare payload
		$body = wp_json_encode( $payload );
		if ( ! $body ) {
			$error_msg = 'Failed to encode secondary transaction payload.';
			$this->log->add( 'ig_scotiabank', 'Fiserv API Error: ' . $error_msg );
			return new \WP_Error( 'payload_encode_error', $error_msg );
		}

		$base_url = $this->sandbox ? self::API_BASE_URL_SANDBOX : self::API_BASE_URL_LIVE;
		$url      = $base_url . '/payments/' . urlencode( $transaction_id );
		$headers = $this->build_headers( $body );

		$response = wp_remote_post(
			$url,
			array(
				'headers' => $headers,
				'body'    => $body,
				'timeout' => 30,
				'sslverify' => true,
			)
		);

		return $this->process_response( $response, 'secondary_transaction' );
	}

	/**
	 * Inquire about a transaction status
	 *
	 * @param string $transaction_id Gateway transaction ID
	 * @return array|WP_Error Response array or WP_Error on failure
	 */
	public function inquiry_transaction( $transaction_id ) {
		if ( empty( $transaction_id ) ) {
			$error_msg = 'Transaction ID is required for inquiry.';
			$this->log->add( 'ig_scotiabank', 'Fiserv API Error: ' . $error_msg );
			return new \WP_Error( 'missing_transaction_id', $error_msg );
		}

		$base_url = $this->sandbox ? self::API_BASE_URL_SANDBOX : self::API_BASE_URL_LIVE;
		$url      = $base_url . '/payments/' . urlencode( $transaction_id );

		// For GET requests, body is empty
		$body    = '';
		$headers = $this->build_headers( $body );

		$response = wp_remote_get(
			$url,
			array(
				'headers'   => $headers,
				'timeout'   => 30,
				'sslverify' => true,
			)
		);

		return $this->process_response( $response, 'transaction_inquiry' );
	}

	/**
	 * Process API response and return standardized result
	 *
	 * @param array|WP_Error $response     Raw response from wp_remote_*
	 * @param string         $request_type Type of request (for logging)
	 * @return array|WP_Error Decoded response or WP_Error
	 */
	private function process_response( $response, $request_type = 'api_call' ) {
		// Check for network errors
		if ( is_wp_error( $response ) ) {
			$error_message = $response->get_error_message();
			$this->log->add(
				'ig_scotiabank',
				sprintf(
					'Fiserv API %s network error: %s',
					$request_type,
					$error_message
				)
			);
			return new \WP_Error( 'api_network_error', $error_message );
		}

		$status_code = wp_remote_retrieve_response_code( $response );
		$body        = wp_remote_retrieve_body( $response );

		// Attempt to decode response
		$decoded = json_decode( $body, true );
		if ( null === $decoded && ! empty( $body ) ) {
			// Non-JSON response
			$this->log->add(
				'ig_scotiabank',
				sprintf(
					'Fiserv API %s non-JSON response (code %d): %s',
					$request_type,
					$status_code,
					substr( $body, 0, 200 )
				)
			);
		}

		// Check HTTP status code
		if ( 200 !== $status_code ) {
			$error_message = isset( $decoded['error'], $decoded['error']['message'] )
				? $decoded['error']['message']
				: 'Gateway returned HTTP ' . $status_code;

			$this->log->add(
				'ig_scotiabank',
				sprintf(
					'Fiserv API %s failed with status %d: %s',
					$request_type,
					$status_code,
					$error_message
				)
			);

			// Add detailed error logging
			$this->log->add( 'ig_scotiabank', 'Fiserv API Error Details:' );
			$this->log->add( 'ig_scotiabank', 'Status Code: ' . $status_code );
			$this->log->add( 'ig_scotiabank', 'Response Body: ' . $body );
			$this->log->add( 'ig_scotiabank', 'Response Headers: ' . wp_json_encode( wp_remote_retrieve_headers( $response ) ) );

			return new \WP_Error(
				'api_error_' . $status_code,
				$error_message,
				array( 'response_code' => $status_code, 'response' => $decoded )
			);
		}

		// Success
		$this->log->add(
			'ig_scotiabank',
			sprintf(
				'Fiserv API %s succeeded (code %d)',
				$request_type,
				$status_code
			)
		);

		return $decoded ? $decoded : array();
	}
}
