<?php
/**
 * Settings REST controller.
 *
 * @package RewindBrevoFormBridge
 */

namespace RewindBrevoFormBridge\Rest\Controllers;

use RewindBrevoFormBridge\Admin\Settings;
use RewindBrevoFormBridge\Rest\BaseController;

defined( 'ABSPATH' ) || exit;

/**
 * GET/POST /rbfb/v1/settings and POST /rbfb/v1/settings/reset.
 *
 * The stored API key never leaves the server: responses carry only the
 * masked hint, a hasKey boolean and where the key came from.
 *
 * @since 0.4.0
 */
class SettingsController extends BaseController {

	/**
	 * Settings storage service.
	 *
	 * @var Settings
	 */
	private Settings $settings;

	/**
	 * Constructor.
	 *
	 * @since 0.4.0
	 * @param Settings $settings Settings storage service.
	 */
	public function __construct( Settings $settings ) {
		parent::__construct( 'settings' );

		$this->settings = $settings;
	}

	/**
	 * Register routes.
	 *
	 * @since 0.4.0
	 * @return void
	 */
	public function register_routes(): void {
		register_rest_route(
			self::NAMESPACE_V1,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_settings' ),
					'permission_callback' => array( $this, 'can_manage' ),
				),
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_settings' ),
					'permission_callback' => array( $this, 'can_manage' ),
					'args'                => $this->schema(),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/' . $this->rest_base . '/reset',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'reset_settings' ),
				'permission_callback' => array( $this, 'can_manage' ),
			)
		);
	}

	/**
	 * Argument schema for a settings write. Every field is optional so the
	 * UI can PATCH one row at a time.
	 *
	 * @since 0.4.0
	 * @return array
	 */
	private function schema(): array {
		return array(
			'api_key'                  => array(
				'type'        => 'string',
				'description' => __( 'A new Brevo API key. Omit or send empty to keep the stored key.', 'rewind-form-bridge-for-brevo' ),
			),
			'default_country'          => array(
				'type'              => 'string',
				'description'       => __( 'ISO 3166-1 alpha-2 country code used to complete bare phone numbers.', 'rewind-form-bridge-for-brevo' ),
				'sanitize_callback' => array( $this, 'sanitize_text' ),
			),
			'log_retention_days'       => array(
				'type'    => 'integer',
				'minimum' => 1,
				'maximum' => 3650,
			),
			'alert_email'              => array(
				'type'              => 'string',
				'validate_callback' => array( $this, 'validate_optional_email' ),
			),
			'alert_failure_threshold'  => array(
				'type'    => 'integer',
				'minimum' => 1,
				'maximum' => 1000,
			),
			'delete_data_on_uninstall' => array(
				'type' => 'boolean',
			),
			'clear_api_key'            => array(
				'type'        => 'boolean',
				'default'     => false,
				'description' => __( 'Remove the stored API key. An empty api_key means "keep the current key", so clearing needs its own explicit flag.', 'rewind-form-bridge-for-brevo' ),
			),

			'email_enabled'            => array(
				'type'        => 'boolean',
				'description' => __( 'Route wp_mail() through Brevo instead of the server default.', 'rewind-form-bridge-for-brevo' ),
			),
			'email_transport'          => array(
				'type' => 'string',
				'enum' => array( 'api', 'smtp' ),
			),
			'email_from_email'         => array(
				'type'              => 'string',
				'validate_callback' => array( $this, 'validate_optional_email' ),
			),
			'email_from_name'          => array(
				'type'              => 'string',
				'sanitize_callback' => array( $this, 'sanitize_text' ),
			),
			'email_reply_to'           => array(
				'type'              => 'string',
				'validate_callback' => array( $this, 'validate_optional_email' ),
			),
			'email_force_from'         => array(
				'type' => 'boolean',
			),
			'email_fallback'           => array(
				'type'        => 'boolean',
				'description' => __( 'Let WordPress fall back to its default mailer when a Brevo send fails.', 'rewind-form-bridge-for-brevo' ),
			),
			'smtp_login'               => array(
				'type'              => 'string',
				'sanitize_callback' => array( $this, 'sanitize_text' ),
			),
			'smtp_key'                 => array(
				'type'        => 'string',
				'description' => __( 'A new Brevo SMTP key. Omit or send empty to keep the stored key.', 'rewind-form-bridge-for-brevo' ),
			),
			'smtp_port'                => array(
				'type' => 'integer',
				'enum' => array( 587, 465, 2525 ),
			),
			'clear_smtp_key'           => array(
				'type'        => 'boolean',
				'default'     => false,
				'description' => __( 'Remove the stored SMTP key.', 'rewind-form-bridge-for-brevo' ),
			),
		);
	}

	/**
	 * Validate an address field that may legitimately be blank.
	 *
	 * The 'email' schema format rejects an empty string, which would make
	 * clearing one of these fields a 400 rather than a save.
	 *
	 * @since 0.5.0
	 * @param mixed $value Raw value.
	 * @return bool
	 */
	public function validate_optional_email( $value ): bool {
		$value = trim( (string) $value );

		return '' === $value || (bool) is_email( $value );
	}

	/**
	 * Read the settings, redacted.
	 *
	 * @since 0.4.0
	 * @return \WP_REST_Response
	 */
	public function get_settings(): \WP_REST_Response {
		return $this->respond( $this->redacted() );
	}

	/**
	 * Write settings through the existing sanitizer.
	 *
	 * @since 0.4.0
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function update_settings( \WP_REST_Request $request ) {
		$current = $this->settings->all();
		$input   = array();

		foreach ( array_keys( $this->settings->defaults() ) as $key ) {
			if ( $request->has_param( $key ) ) {
				$input[ $key ] = $request->get_param( $key );
			} elseif ( 'api_key' !== $key && 'smtp_key' !== $key ) {
				$input[ $key ] = $current[ $key ];
			}
		}

		$clean = $this->settings->sanitize( $input );

		// sanitize() reads an empty api_key as "keep the stored one", which
		// is right for an ordinary save but leaves no way to disconnect.
		// The explicit flag is that way, and it cannot fire by accident.
		if ( true === $request->get_param( 'clear_api_key' ) && 'constant' !== $this->settings->api_key_source() ) {
			$clean['api_key'] = '';
			delete_option( 'rbfb_last_verified' );
		}

		if ( true === $request->get_param( 'clear_smtp_key' ) && 'constant' !== $this->settings->smtp_key_source() ) {
			$clean['smtp_key'] = '';
		}

		update_option( Settings::OPTION, $clean, false );

		return $this->respond( $this->redacted() );
	}

	/**
	 * Restore defaults, keeping the stored API key.
	 *
	 * @since 0.4.0
	 * @return \WP_REST_Response
	 */
	public function reset_settings(): \WP_REST_Response {
		$defaults             = $this->settings->defaults();
		$defaults['api_key']  = (string) $this->settings->get( 'api_key', '' );
		$defaults['smtp_key'] = (string) $this->settings->get( 'smtp_key', '' );

		update_option( Settings::OPTION, $defaults, false );

		return $this->respond( $this->redacted() );
	}

	/**
	 * The settings array with the key replaced by safe metadata.
	 *
	 * @since 0.4.0
	 * @return array
	 */
	private function redacted(): array {
		$all = $this->settings->all();

		unset( $all['api_key'], $all['smtp_key'] );

		$all['has_key']         = '' !== $this->settings->api_key();
		$all['key_hint']        = $this->settings->masked_key();
		$all['key_source']      = $this->settings->api_key_source();
		$all['has_smtp_key']    = '' !== $this->settings->smtp_key();
		$all['smtp_key_hint']   = $this->settings->masked_smtp_key();
		$all['smtp_key_source'] = $this->settings->smtp_key_source();

		return $all;
	}
}
