<?php
namespace TeknoTok\AiTranslator\Providers;

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

use TeknoTok\AiTranslator\Integrations\LicenseManager;
use TeknoTok\AiTranslator\Utils\ErrorSanitizer;
use TeknoTok\AiTranslator\Utils\Logger;

/**
 * TeknoTok Translation Provider implementation.
 * Connects to the central gateway API on soft.teknotok.com.
 */
class TeknoTokProvider implements ProviderInterface {

	/**
	 * Translate text strings or arrays.
	 *
	 * @param string|array $text        String or array to translate.
	 * @param string       $source_lang Source locale.
	 * @param string       $target_lang Target locale.
	 * @return string|array Translated output.
	 * @throws \Exception
	 */
	public function translate( $text, $source_lang, $target_lang ) {
		$settings = get_option( 'teknotok_ait_settings', array() );
		$tone     = $settings['translation_tone'] ?? 'standard';

		$license_data = LicenseManager::get_license_data();
		$license_key  = $license_data['license_key'] ?? '';

		$is_array = is_array( $text );

		if ( 0 === strpos( $license_key, 'TEKNOTOK-TEST-' ) ) {
			return $this->mock_test_translation( $text, $target_lang );
		}

		$body = array(
			'text'         => $text,
			'source_lang'  => $source_lang,
			'target_lang'  => $target_lang,
			'tone'         => $tone,
			'license_key'  => $license_key,
			'product_slug' => TEKNOTOK_AIT_PRODUCT_SLUG,
			'site_url'     => get_site_url(),
		);

		$item_count = $is_array ? count( $text ) : 1;
		$char_count = $is_array ? strlen( wp_json_encode( $text ) ) : strlen( $text );
		$is_local = $this->can_use_local_gateway();

		Logger::log( sprintf( '[TeknoTok Gateway] Dispatching request (endpoint="%s", items=%d, chars=%d, source="%s", target="%s")', $is_local ? 'local-rest' : 'remote-api', $item_count, $char_count, $source_lang, $target_lang ), 'info' );

		$max_retries   = 2;
		$response      = null;
		$code          = 0;
		$response_body = '';

		for ( $attempt = 1; $attempt <= $max_retries; $attempt++ ) {
			// Introduce a pacing delay (300ms) on the first attempt, or exponential backoff if retrying
			if ( 1 === $attempt ) {
				usleep( 300000 );
			} else {
				// Exponential backoff with jitter: 2s, 4s, 8s (+ random 0-500ms)
				$delay = ( pow( 2, $attempt - 1 ) * 1 ) + ( rand( 0, 500 ) / 1000 );
				Logger::log( sprintf( '  [TeknoTok Gateway] Retry attempt %d/%d after %ss backoff...', $attempt, $max_retries, round($delay, 2) ), 'info' );
				usleep( $delay * 1000000 );
			}

			$start_time = microtime( true );
			$response = $this->request_gateway( $body );
			$elapsed = round( microtime( true ) - $start_time, 2 );

			if ( ! is_wp_error( $response ) ) {
				$code          = wp_remote_retrieve_response_code( $response );
				$response_body = wp_remote_retrieve_body( $response );
				
				Logger::log( sprintf( '  [TeknoTok Gateway] Attempt %d/%d returned HTTP %d in %ss.', $attempt, $max_retries, $code, $elapsed ), 'info' );

				if ( 200 === $code ) {
					break; // Success!
				}

				// Check for permanent client-side errors (400 to 499, except 429 which is rate limit)
				if ( $code >= 400 && $code < 500 && 429 !== $code ) {
					Logger::log( sprintf( '  [TeknoTok Gateway] Client-side error (HTTP %d) is permanent. Aborting retries.', $code ), 'warning' );
					break; // Stop retrying on permanent failures (e.g. quota exceeded 402, invalid key 403, missing field 400)
				}

				if ( $code >= 500 && ( false !== stripos( $response_body, 'cURL error 28' ) || false !== stripos( $response_body, 'timed out' ) ) ) {
					Logger::log( '  [TeknoTok Gateway] Server-side timeout error. Aborting retries.', 'warning' );
					break;
				}
			} else {
				Logger::log( sprintf( '  [TeknoTok Gateway] Attempt %d/%d request failed after %ss: %s', $attempt, $max_retries, $elapsed, ErrorSanitizer::sanitize_message( $response->get_error_message() ) ), 'warning' );
				if ( $this->is_timeout_error( $response->get_error_message() ) ) {
					Logger::log( '  [TeknoTok Gateway] Timeout error received. Aborting retries to protect page load time.', 'warning' );
					break;
				}
			}
		}

		if ( is_wp_error( $response ) ) {
			$safe_message = ErrorSanitizer::sanitize_message( $response->get_error_message() );
			Logger::log( '[TeknoTok Gateway Error] Request failed: ' . $safe_message, 'error' );
			throw new \Exception( $safe_message );
		}

		if ( 200 !== $code ) {
			$error_data = json_decode( $response_body, true );
			if ( is_array( $error_data ) && 'quota_exceeded' === ( $error_data['code'] ?? '' ) ) {
				$this->store_quota_error_data( $error_data );
				$msg = ErrorSanitizer::quota_exceeded_message();
			} else {
				$msg = ErrorSanitizer::sanitize_message( $error_data['message'] ?? sprintf( __( 'Translation request failed with code %d', 'teknotok-ai-translator' ), $code ) );
			}
			Logger::log( sprintf( '[TeknoTok Gateway Error] Response code %d received. %s', $code, $msg ), 'error' );
			throw new \Exception( $msg, $code );
		}

		$data = json_decode( $response_body, true );
		if ( empty( $data ) || ! isset( $data['success'] ) || ! $data['success'] ) {
			$msg = ErrorSanitizer::sanitize_message( $data['message'] ?? __( 'Failed to retrieve valid translation from gateway.', 'teknotok-ai-translator' ) );
			Logger::log( '[TeknoTok Gateway Error] Success flag is false. Msg: ' . $msg, 'error' );
			throw new \Exception( $msg );
		}

		$translated = $data['translation'] ?? '';

		if ( empty( $translated ) ) {
			Logger::log( '[TeknoTok Gateway Error] Received empty translation text field in response body.', 'error' );
			throw new \Exception( __( 'Received empty translation response from gateway.', 'teknotok-ai-translator' ) );
		}

		Logger::log( sprintf( '[TeknoTok Gateway Success] Received successful translation (chars=%d).', strlen( is_array( $translated ) ? wp_json_encode( $translated ) : $translated ) ), 'info' );

		if ( $is_array ) {
			if ( ! is_array( $translated ) ) {
				Logger::log( '[TeknoTok Gateway Error] Failed parsing gateway translation array response: ' . wp_json_encode( $translated ), 'error' );
				throw new \Exception( __( 'Gateway did not return a valid translation mapping.', 'teknotok-ai-translator' ) );
			}
			return $translated;
		}

		return trim( $translated );
	}

	/**
	 * Store quota details from a quota-exceeded gateway response.
	 *
	 * @param array $error_data Gateway error data.
	 * @return void
	 */
	private function store_quota_error_data( $error_data ) {
		$license = LicenseManager::get_license_data();
		$limit   = max( 0, (int) ( $error_data['words_limit'] ?? ( $license['words_limit'] ?? 0 ) ) );
		$used    = max( 0, (int) ( $error_data['words_used'] ?? ( $license['words_used'] ?? $limit ) ) );

		$license['plan_slug'] = function_exists( 'sanitize_key' ) ? sanitize_key( $error_data['plan_slug'] ?? ( $license['plan_slug'] ?? 'free' ) ) : preg_replace( '/[^a-z0-9_\-]/', '', strtolower( (string) ( $error_data['plan_slug'] ?? ( $license['plan_slug'] ?? 'free' ) ) ) );
		$license['words_limit'] = $limit;
		$license['words_used'] = $used;
		$license['words_remaining'] = max( 0, (int) ( $error_data['words_remaining'] ?? max( 0, $limit - $used ) ) );
		$license['quota_period'] = function_exists( 'sanitize_key' ) ? sanitize_key( $error_data['quota_period'] ?? ( $license['quota_period'] ?? '' ) ) : preg_replace( '/[^a-z0-9_\-]/', '', strtolower( (string) ( $error_data['quota_period'] ?? ( $license['quota_period'] ?? '' ) ) ) );
		$license['quota_period_end'] = function_exists( 'sanitize_text_field' ) ? sanitize_text_field( $error_data['quota_period_end'] ?? ( $license['quota_period_end'] ?? '' ) ) : trim( strip_tags( (string) ( $error_data['quota_period_end'] ?? ( $license['quota_period_end'] ?? '' ) ) ) );
		$license['user_message'] = ErrorSanitizer::quota_exceeded_message();
		$license['last_check'] = time();

		update_option( 'teknotok_ait_license_status', $license, false );
		if ( function_exists( 'delete_transient' ) ) {
			delete_transient( 'teknotok_ait_quota_status_cache' );
			delete_transient( 'teknotok_ait_free_usage_cache' );
		}
	}

	/**
	 * Return deterministic clean output for local/staging test license keys.
	 *
	 * @param string|array $text        Source text or mapping.
	 * @param string       $target_lang Target locale.
	 * @return string|array Test translated output.
	 */
	private function mock_test_translation( $text, $target_lang ) {
		if ( is_array( $text ) ) {
			$translated = array();
			foreach ( $text as $key => $value ) {
				$translated[ $key ] = (string) $value;
			}
			return $translated;
		}

		return (string) $text;
	}

	/**
	 * Send a translation request to the configured gateway.
	 *
	 * @param array $body Request body.
	 * @return array|\WP_Error HTTP-like response.
	 */
	private function request_gateway( $body ) {
		if ( $this->can_use_local_gateway() ) {
			$request = new \WP_REST_Request( 'POST', '/teknotok-translator/v1/translate' );
			foreach ( $body as $key => $value ) {
				$request->set_param( $key, $value );
			}

			$response = \TKNTK_LM_REST::translate( $request );
			if ( $response instanceof \WP_REST_Response ) {
				return array(
					'response' => array( 'code' => $response->get_status() ),
					'body'     => wp_json_encode( $response->get_data() ),
				);
			}
		}

		return wp_remote_post(
			TEKNOTOK_AIT_TRANSLATE_API,
			array(
				'headers' => array(
					'Content-Type' => 'application/json',
				),
				'body'    => wp_json_encode( $body ),
				'timeout' => $this->get_request_timeout(),
			)
		);
	}

	/**
	 * Get a request timeout that stays below the current PHP execution limit.
	 *
	 * @return int Timeout in seconds.
	 */
	private function get_request_timeout() {
		$server_limit = (int) ini_get( 'max_execution_time' );
		$timeout      = 20;

		if ( $server_limit > 0 ) {
			$timeout = max( 5, min( 20, $server_limit - 5 ) );
		}

		return (int) apply_filters( 'teknotok_ait_gateway_request_timeout', $timeout );
	}

	/**
	 * Check whether an HTTP error message is a timeout.
	 *
	 * @param string $message Error message.
	 * @return bool True for timeout-like errors.
	 */
	private function is_timeout_error( $message ) {
		$message = strtolower( (string) $message );

		return false !== strpos( $message, 'timeout' )
			|| false !== strpos( $message, 'timed out' )
			|| false !== strpos( $message, 'cURL error 28' );
	}

	/**
	 * Check whether the gateway is the same WordPress install as this plugin.
	 *
	 * @return bool True when local dispatch is available.
	 */
	private function can_use_local_gateway() {
		if ( ! class_exists( '\TKNTK_LM_REST' ) || ! class_exists( '\WP_REST_Request' ) ) {
			return false;
		}

		$gateway = wp_parse_url( TEKNOTOK_AIT_GATEWAY_BASE_URL );
		$home    = wp_parse_url( home_url() );

		if ( empty( $gateway['host'] ) || empty( $home['host'] ) || strtolower( $gateway['host'] ) !== strtolower( $home['host'] ) ) {
			return false;
		}

		$gateway_port = isset( $gateway['port'] ) ? (int) $gateway['port'] : null;
		$home_port    = isset( $home['port'] ) ? (int) $home['port'] : null;

		return $gateway_port === $home_port;
	}

	/**
	 * Test key by sending a cheap request.
	 *
	 * @return bool
	 */
	public function validate_key() {
		try {
			$this->translate( 'Test', 'en', 'es' );
			return true;
		} catch ( \Exception $e ) {
			return false;
		}
	}

	/**
	 * Check if provider is configured (e.g. API credentials are set).
	 *
	 * @return bool True if configured, false otherwise.
	 */
	public function is_configured() {
		// The gateway provider is built-in and always available, no credentials setup is required
		return true;
	}
}
