<?php
/**
 * REST API endpoints for Mail Doctor.
 *
 * @package MailDoctor
 */

namespace MailDoctor;

use MailDoctor\API_Keys;
use MailDoctor\Helpers\Settings;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

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

/**
 * Register REST API routes for external integrations.
 */
class Rest_API {

	/**
	 * REST namespace.
	 */
	const NAMESPACE = 'maildoctor/v1';

	/**
	 * Bootstrap hooks.
	 *
	 * @return void
	 */
	public static function init(): void {
		add_action( 'rest_api_init', array( __CLASS__, 'register_routes' ) );
	}

	/**
	 * Register REST routes.
	 *
	 * @return void
	 */
	public static function register_routes(): void {
		register_rest_route(
			self::NAMESPACE,
			'/logs',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( __CLASS__, 'get_logs' ),
				'permission_callback' => static function ( WP_REST_Request $request ) {
					return self::check_permissions( $request, Capabilities::VIEW_LOGS );
				},
				'args'                => array(
					'status'      => array(
						'type'              => 'string',
						'required'          => false,
						'validate_callback' => static function ( $value ) {
							$allowed = array( 'queued', 'sent', 'failed', 'skipped' );
							return in_array( $value, $allowed, true );
						},
					),
					'event'       => array(
						'type'     => 'string',
						'required' => false,
					),
					'search'      => array(
						'type'     => 'string',
						'required' => false,
					),
					'email_class' => array(
						'type'     => 'string',
						'required' => false,
					),
					'page'        => array(
						'type'    => 'integer',
						'default' => 1,
						'minimum' => 1,
					),
					'per_page'    => array(
						'type'    => 'integer',
						'default' => 20,
						'minimum' => 1,
						'maximum' => 100,
					),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/retry/(?P<id>\d+)',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( __CLASS__, 'post_retry' ),
				'permission_callback' => static function ( WP_REST_Request $request ) {
					return self::check_permissions( $request, Capabilities::MANAGE_RETRIES );
				},
				'args'                => array(
					'id' => array(
						'required' => true,
						'type'     => 'integer',
						'minimum'  => 1,
					),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/status',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( __CLASS__, 'get_status' ),
				'permission_callback' => static function ( WP_REST_Request $request ) {
					return self::check_permissions( $request, Capabilities::MANAGE_SETTINGS );
				},
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/test-send',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( __CLASS__, 'post_test_send' ),
				'permission_callback' => static function ( WP_REST_Request $request ) {
					return self::check_permissions( $request, Capabilities::MANAGE_SETTINGS );
				},
				'args'                => array(
					'to' => array(
						'type'     => 'string',
						'required' => false,
					),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/queue',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( __CLASS__, 'get_queue' ),
				'permission_callback' => static function ( WP_REST_Request $request ) {
					return self::check_permissions( $request, Capabilities::MANAGE_RETRIES );
				},
				'args'                => array(
					'limit' => array(
						'type'    => 'integer',
						'default' => 25,
						'minimum' => 1,
						'maximum' => 100,
					),
				),
			)
		);
	}

	/**
	 * REST callback: return paginated logs.
	 *
	 * @param WP_REST_Request $request HTTP request.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_logs( WP_REST_Request $request ) {
		$args = array(
			'status'      => $request->get_param( 'status' ),
			'event'       => $request->get_param( 'event' ),
			'search'      => $request->get_param( 'search' ),
			'email_class' => $request->get_param( 'email_class' ),
			'paged'       => max( 1, (int) $request->get_param( 'page' ) ),
			'per_page'    => max( 1, min( 100, (int) $request->get_param( 'per_page' ) ) ),
		);

		$results = Logger::query( $args );

		$items = array();

		foreach ( (array) $results['items'] as $row ) {
			if ( is_array( $row ) ) {
				$items[] = self::prepare_log_item( $row );
			}
		}

		return rest_ensure_response(
			array(
				'items' => $items,
				'total' => (int) ( $results['total'] ?? 0 ),
			)
		);
	}

	/**
	 * REST callback: trigger manual retry for a log.
	 *
	 * @param WP_REST_Request $request HTTP request.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function post_retry( WP_REST_Request $request ) {
		$log_id = (int) $request->get_param( 'id' );

		if ( $log_id <= 0 ) {
			return new WP_Error(
				'mail_doctor_invalid_log',
				__( 'A valid log identifier must be supplied.', 'mail-doctor' ),
				array( 'status' => 400 )
			);
		}

		$result = Retry::manual_retry( $log_id );

		if ( $result instanceof WP_Error ) {
			return $result;
		}

		$log = Logger::get_entry( $log_id );

		return rest_ensure_response(
			array(
				'success' => true,
				'log'     => $log ? self::prepare_log_item( $log ) : null,
			)
		);
	}

	/**
	 * REST callback: provide system status summary.
	 *
	 * @return WP_REST_Response
	 */
	public static function get_status(): WP_REST_Response {
		$settings = Helpers\Settings::all();

		return rest_ensure_response(
			array(
				'queue'        => Checks::queue_stats(),
				'settings'     => array(
					'privacy_mode'      => ! empty( $settings['privacy_mode'] ),
					'hash_recipients'   => ! empty( $settings['hash_recipients'] ),
					'retention_periods' => Helpers\Settings::retention_periods(),
				),
				'secrets'      => array(
					'encryption_supported' => Secrets::encryption_supported(),
				),
				'smtp'         => Mailer::test_connection(),
				'capabilities' => array(
					'manage_settings' => Capabilities::current_user_can( Capabilities::MANAGE_SETTINGS ),
					'manage_retries'  => Capabilities::current_user_can( Capabilities::MANAGE_RETRIES ),
					'view_logs'       => Capabilities::current_user_can( Capabilities::VIEW_LOGS ),
				),
			)
		);
	}

	/**
	 * REST callback: trigger test email send.
	 *
	 * @param WP_REST_Request $request HTTP request.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function post_test_send( WP_REST_Request $request ) {
		$recipient = $request->get_param( 'to' );
		$recipient = $recipient ? sanitize_email( (string) $recipient ) : get_option( 'admin_email' );

		if ( ! $recipient || ! is_email( $recipient ) ) {
			return new WP_Error(
				'mail_doctor_invalid_email',
				__( 'A valid recipient email address is required.', 'mail-doctor' ),
				array( 'status' => 400 )
			);
		}

		Mailer::force_next_override();
		Mailer::set_event_context( 'mail_doctor_test' );

		$subject = __( 'Mail Doctor Test Email', 'mail-doctor' );
		$message = sprintf(
			/* translators: %s: site name. */
			__( "Hello from %s!\n\nThis diagnostic email was sent via the Mail Doctor REST API.\n\nIf you received this message, SMTP connectivity and authentication worked as expected.", 'mail-doctor' ),
			wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES )
		);

		$headers = array( 'Content-Type: text/plain; charset=UTF-8' );
		$start   = microtime( true );
		$result  = wp_mail( $recipient, $subject, $message, $headers );
		$latency = (int) round( ( microtime( true ) - $start ) * 1000 );

		Mailer::clear_event_context();

		if ( ! $result ) {
			return new WP_Error(
				'mail_doctor_send_failed',
				__( 'The test email could not be sent. Review SMTP settings and try again.', 'mail-doctor' ),
				array( 'status' => 500 )
			);
		}

		$log = Logger::latest_by_event( 'mail_doctor_test' );

		return rest_ensure_response(
			array(
				'success'    => true,
				'latency_ms' => $latency,
				'recipient'  => $recipient,
				'log'        => $log ? self::prepare_log_item( $log ) : null,
			)
		);
	}

	/**
	 * REST callback: expose retry queue timeline.
	 *
	 * @param WP_REST_Request $request HTTP request.
	 * @return WP_REST_Response
	 */
	public static function get_queue( WP_REST_Request $request ): WP_REST_Response {
		$limit = max( 1, min( 100, (int) $request->get_param( 'limit' ) ) );
		$rows  = Logger::get_retry_timeline( $limit );

		$items = array();

		foreach ( $rows as $row ) {
			$item = $row;

			$item['recipients'] = self::resolve_recipient_meta(
				array(
					'recipients_plain' => $row['recipients_plain'] ?? array(),
					'recipients_hash'  => $row['recipients_hash'] ?? array(),
					'privacy_mode'     => ! empty( $row['privacy_mode'] ),
				),
				$row['to_email'] ?? ''
			);

			unset( $item['recipients_plain'], $item['recipients_hash'], $item['privacy_mode'] );

			$items[] = $item;
		}

		return rest_ensure_response(
			array(
				'items' => $items,
			)
		);
	}

	/**
	 * Check nonce and capability requirements for REST access.
	 *
	 * @param WP_REST_Request $request    HTTP request instance.
	 * @param string          $capability Capability to validate.
	 * @return true|WP_Error
	 */
	protected static function check_permissions( WP_REST_Request $request, string $capability ) {
		$api_key_auth = self::resolve_api_key_auth( $request );

		if ( is_wp_error( $api_key_auth ) ) {
			return $api_key_auth;
		}

		if ( is_array( $api_key_auth ) ) {
			if ( ! self::api_key_allows_capability( $api_key_auth, $capability, $request ) ) {
				return new WP_Error(
					'mail_doctor_api_key_forbidden',
					__( 'The provided API key does not allow this action.', 'mail-doctor' ),
					array( 'status' => 403 )
				);
			}

			self::set_request_attribute( $request, 'mail_doctor_api_key', $api_key_auth['id'] );
			self::set_request_attribute( $request, 'mail_doctor_api_scopes', $api_key_auth['scopes'] ?? array() );

			return true;
		}

		if ( ! self::validate_nonce( $request ) ) {
			return new WP_Error(
				'mail_doctor_invalid_nonce',
				__( 'A valid REST API nonce is required.', 'mail-doctor' ),
				array( 'status' => 403 )
			);
		}

		if ( ! Capabilities::current_user_can( $capability ) ) {
			return new WP_Error(
				'mail_doctor_forbidden',
				__( 'You do not have permission to access this resource.', 'mail-doctor' ),
				array( 'status' => 403 )
			);
		}

		return true;
	}

	/**
	 * Compat helper for storing request attributes across WordPress versions.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @param string          $key     Attribute key.
	 * @param mixed           $value   Attribute value.
	 * @return void
	 */
	protected static function set_request_attribute( WP_REST_Request $request, string $key, $value ): void {
		if ( method_exists( $request, 'set_attribute' ) ) {
			$request->set_attribute( $key, $value );
			return;
		}

		if ( method_exists( $request, 'offsetSet' ) ) {
			$request->offsetSet( $key, $value );
			return;
		}

		$request->$key = $value;
	}

	/**
	 * Validate REST nonce header or request param.
	 *
	 * @param WP_REST_Request $request HTTP request.
	 * @return bool
	 */
	protected static function validate_nonce( WP_REST_Request $request ): bool {
		$nonce = $request->get_header( 'X-WP-Nonce' );

		if ( ! $nonce ) {
			$nonce = $request->get_param( '_wpnonce' );
		}

		if ( ! $nonce ) {
			return false;
		}

		return (bool) wp_verify_nonce( sanitize_text_field( (string) $nonce ), 'wp_rest' );
	}

	/**
	 * Try to authenticate request via API key header.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return array<string, mixed>|WP_Error|null
	 */
	protected static function resolve_api_key_auth( WP_REST_Request $request ) {
		$provided = $request->get_header( 'x-maildoctor-key' );

		if ( ! $provided ) {
			$provided = $request->get_header( 'x-maildoctor-api-key' );
		}

		if ( ! $provided ) {
			// Support query/body param for server-to-server integrations.
			$provided = $request->get_param( '_md_api_key' );
		}

		if ( ! $provided ) {
			return null;
		}

		$key = API_Keys::validate_secret( sanitize_text_field( (string) $provided ) );

		if ( ! $key ) {
			API_Keys::record_failure( 'invalid_secret' );

			return new WP_Error(
				'mail_doctor_api_key_invalid',
				__( 'Invalid Mail Doctor API key.', 'mail-doctor' ),
				array( 'status' => 403 )
			);
		}

		if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
			API_Keys::touch( $key['id'], sanitize_text_field( wp_unslash( (string) $_SERVER['REMOTE_ADDR'] ) ) );
		} else {
			API_Keys::touch( $key['id'] );
		}

		return $key;
	}

	/**
	 * Determine whether API key scopes allow the required capability.
	 *
	 * @param array<string, mixed> $key        API key metadata.
	 * @param string               $capability Capability being requested.
	 * @return bool
	 */
	protected static function api_key_allows_capability( array $key, string $capability, WP_REST_Request $request ): bool {
		if ( API_Keys::has_scope( $key, 'all' ) ) {
			return true;
		}

		switch ( $capability ) {
			case Capabilities::MANAGE_SETTINGS:
				if ( API_Keys::has_scope( $key, 'settings.manage' ) ) {
					return true;
				}

				$route = $request->get_route();
				if ( is_string( $route ) && false !== strpos( $route, '/status' ) ) {
					return API_Keys::has_scope( $key, 'status.read' );
				}

				return false;

			case Capabilities::MANAGE_RETRIES:
				return API_Keys::has_scope( $key, 'queue.write' );

			case Capabilities::VIEW_LOGS:
				return API_Keys::has_scope( $key, 'logs.read' );

			default:
				return false;
		}
	}

	/**
	 * Convert raw log row into safe response structure.
	 *
	 * @param array<string, mixed> $row Database row.
	 * @return array<string, mixed>
	 */
	protected static function prepare_log_item( array $row ): array {
		$payload = array();

		if ( isset( $row['payload'] ) && $row['payload'] ) {
			$decoded = json_decode( (string) $row['payload'], true );
			if ( is_array( $decoded ) ) {
				$payload = $decoded;
			}
		} elseif ( isset( $row['id'] ) ) {
			$payload = Logger::get_payload( (int) $row['id'] );
		}

		$meta              = isset( $payload['meta'] ) && is_array( $payload['meta'] ) ? $payload['meta'] : array();
		$attachments_meta  = isset( $meta['attachments_meta'] ) && is_array( $meta['attachments_meta'] ) ? array_values( $meta['attachments_meta'] ) : array();
		$attachments_total = isset( $meta['attachment_total_bytes'] ) ? (int) $meta['attachment_total_bytes'] : 0;
		$async_meta        = isset( $meta['async'] ) && is_array( $meta['async'] ) ? $meta['async'] : array();
		$fallback_events   = isset( $meta['fallback'] ) && is_array( $meta['fallback'] ) ? array_values( $meta['fallback'] ) : array();
		$transport_key     = isset( $meta['transport'] ) ? sanitize_key( (string) $meta['transport'] ) : '';
		$transport_label   = isset( $meta['transport_label'] ) ? sanitize_text_field( (string) $meta['transport_label'] ) : '';

		if ( '' === $transport_label && '' !== $transport_key ) {
			$transport_label = Settings::transport_label( $transport_key );
		}

		$profile_label = isset( $meta['smtp_profile_label'] ) ? sanitize_text_field( (string) $meta['smtp_profile_label'] ) : '';
		$smtp_host     = isset( $meta['smtp_host'] ) ? sanitize_text_field( (string) $meta['smtp_host'] ) : '';

		return array(
			'id'                      => isset( $row['id'] ) ? (int) $row['id'] : 0,
			'created_at_gmt'          => $row['created_at'] ?? null,
			'event'                   => $row['event'] ?? '',
			'order_id'                => isset( $row['order_id'] ) ? (int) $row['order_id'] : null,
			'subject'                 => $row['subject'] ?? '',
			'status'                  => $row['status'] ?? '',
			'provider_code'           => $row['provider_code'] ?? '',
			'error'                   => $row['error'] ?? '',
			'duration_ms'             => isset( $row['duration_ms'] ) ? (int) $row['duration_ms'] : null,
			'idempotency_key'         => isset( $meta['idempotency_key'] ) ? $meta['idempotency_key'] : null,
			'smtp_profile'            => isset( $meta['smtp_profile'] ) ? $meta['smtp_profile'] : null,
			'smtp_profile_label'      => $profile_label,
			'smtp_host'               => $smtp_host,
			'transport'               => $transport_key,
			'transport_label'         => $transport_label,
			'recipients'              => self::resolve_recipient_meta( $meta, $row['to_email'] ?? '' ),
			'attempts'                => isset( $payload['retry']['attempts'] ) ? (int) $payload['retry']['attempts'] : 0,
			'next_attempt_gmt'        => $payload['retry']['next_attempt_gmt'] ?? null,
			'quarantined'             => ! empty( $payload['retry']['quarantined'] ),
			'attachments_meta'        => $attachments_meta,
			'attachments_total_bytes' => $attachments_total,
			'async_mode'              => $async_meta['mode'] ?? null,
			'fallback_events'         => $fallback_events,
		);
	}

	/**
	 * Hydrate recipient list based on privacy mode.
	 *
	 * @param array<string, mixed> $meta     Payload metadata.
	 * @param string               $fallback Raw stored recipients.
	 * @return array<int, string>
	 */
	protected static function resolve_recipient_meta( array $meta, string $fallback ): array {
		if ( ! empty( $meta['recipients_plain'] ) && is_array( $meta['recipients_plain'] ) ) {
			return array_values(
				array_filter(
					array_map( 'sanitize_email', $meta['recipients_plain'] )
				)
			);
		}

		if ( ! empty( $meta['recipients_hash'] ) && is_array( $meta['recipients_hash'] ) ) {
			return array_values(
				array_filter(
					array_map( 'sanitize_text_field', $meta['recipients_hash'] )
				)
			);
		}

		if ( empty( $fallback ) ) {
			return array();
		}

		$parts = array_map( 'trim', preg_split( '/,|;/', (string) $fallback ) ?: array() );
		$list  = array();

		foreach ( $parts as $value ) {
			$email = sanitize_email( $value );
			if ( $email ) {
				$list[] = $email;
			}
		}

		return $list;
	}
}
