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

namespace RewindBrevoFormBridge\Rest\Controllers;

use RewindBrevoFormBridge\Adapters\Registry;
use RewindBrevoFormBridge\Feeds\Feed;
use RewindBrevoFormBridge\Feeds\Repository;
use RewindBrevoFormBridge\Logs\LogRepository;
use RewindBrevoFormBridge\Rest\BaseController;

defined( 'ABSPATH' ) || exit;

/**
 * CRUD for feeds, plus duplicate.
 *
 * Validation mirrors what the retired admin-ajax handler enforced: a feed
 * needs a title, a known adapter, a form, at least one list, a mapped email
 * field, and an explicit consent decision.
 *
 * @since 0.4.0
 */
class FeedsController extends BaseController {

	/**
	 * Feed repository.
	 *
	 * @var Repository
	 */
	private Repository $feeds;

	/**
	 * Adapter registry.
	 *
	 * @var Registry
	 */
	private Registry $registry;

	/**
	 * Log repository, for the last-sync column.
	 *
	 * @var LogRepository
	 */
	private LogRepository $logs;

	/**
	 * Constructor.
	 *
	 * @since 0.4.0
	 * @param Repository    $feeds    Feed repository.
	 * @param Registry      $registry Adapter registry.
	 * @param LogRepository $logs     Log repository.
	 */
	public function __construct( Repository $feeds, Registry $registry, LogRepository $logs ) {
		parent::__construct( 'feeds' );

		$this->feeds    = $feeds;
		$this->registry = $registry;
		$this->logs     = $logs;
	}

	/**
	 * 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_feeds' ),
					'permission_callback' => array( $this, 'can_manage' ),
				),
				array(
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_feed' ),
					'permission_callback' => array( $this, 'can_manage' ),
					'args'                => $this->schema(),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/' . $this->rest_base . '/(?P<id>\d+)',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_feed' ),
					'permission_callback' => array( $this, 'can_manage' ),
					'args'                => $this->id_arg(),
				),
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_feed' ),
					'permission_callback' => array( $this, 'can_manage' ),
					'args'                => array_merge( $this->id_arg(), $this->schema() ),
				),
				array(
					'methods'             => \WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_feed' ),
					'permission_callback' => array( $this, 'can_manage' ),
					'args'                => $this->id_arg(),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/' . $this->rest_base . '/(?P<id>\d+)/duplicate',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'duplicate_feed' ),
				'permission_callback' => array( $this, 'can_manage' ),
				'args'                => $this->id_arg(),
			)
		);
	}

	/**
	 * The shared id path argument.
	 *
	 * @since 0.4.0
	 * @return array
	 */
	private function id_arg(): array {
		return array(
			'id' => array(
				'type'              => 'integer',
				'required'          => true,
				'minimum'           => 1,
				'sanitize_callback' => 'absint',
			),
		);
	}

	/**
	 * Argument schema for a feed write.
	 *
	 * @since 0.4.0
	 * @return array
	 */
	private function schema(): array {
		return array(
			'title'   => array(
				'type'              => 'string',
				'required'          => true,
				'sanitize_callback' => array( $this, 'sanitize_text' ),
			),
			'adapter' => array(
				'type'              => 'string',
				'required'          => true,
				'sanitize_callback' => 'sanitize_key',
			),
			'form_id' => array(
				'type'              => 'string',
				'required'          => true,
				'sanitize_callback' => array( $this, 'sanitize_text' ),
			),
			'status'  => array(
				'type'    => 'string',
				'default' => 'active',
				'enum'    => array( 'active', 'paused' ),
			),
			'config'  => array(
				'type'     => 'object',
				'required' => true,
			),
		);
	}

	/**
	 * List every feed.
	 *
	 * @since 0.4.0
	 * @return \WP_REST_Response
	 */
	public function get_feeds(): \WP_REST_Response {
		$items = array();

		foreach ( $this->feeds->all( 200 ) as $feed ) {
			$items[] = $this->shape( $feed );
		}

		return $this->respond(
			array(
				'items' => $items,
				'total' => $this->feeds->count(),
			)
		);
	}

	/**
	 * One feed.
	 *
	 * @since 0.4.0
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function get_feed( \WP_REST_Request $request ) {
		$feed = $this->feeds->find( (int) $request->get_param( 'id' ) );

		if ( null === $feed ) {
			return $this->error( 'rbfb_feed_not_found', __( 'That feed no longer exists.', 'rewind-form-bridge-for-brevo' ), self::HTTP_NOT_FOUND );
		}

		return $this->respond( $this->shape( $feed ) );
	}

	/**
	 * Create a feed.
	 *
	 * @since 0.4.0
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function create_feed( \WP_REST_Request $request ) {
		$validated = $this->validate( $request );

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

		$id = $this->feeds->insert(
			$validated['adapter'],
			$validated['form_id'],
			$validated['title'],
			$validated['status'],
			$validated['config']
		);

		if ( 0 === $id ) {
			return $this->error( 'rbfb_db_error', __( 'The feed could not be saved to the database.', 'rewind-form-bridge-for-brevo' ), self::HTTP_SERVER_ERROR );
		}

		$feed = $this->feeds->find( $id );

		return $this->respond( null !== $feed ? $this->shape( $feed ) : array( 'id' => $id ), self::HTTP_CREATED );
	}

	/**
	 * Update a feed.
	 *
	 * @since 0.4.0
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function update_feed( \WP_REST_Request $request ) {
		$id = (int) $request->get_param( 'id' );

		if ( null === $this->feeds->find( $id ) ) {
			return $this->error( 'rbfb_feed_not_found', __( 'That feed no longer exists.', 'rewind-form-bridge-for-brevo' ), self::HTTP_NOT_FOUND );
		}

		$validated = $this->validate( $request );

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

		$ok = $this->feeds->update(
			$id,
			$validated['adapter'],
			$validated['form_id'],
			$validated['title'],
			$validated['status'],
			$validated['config']
		);

		if ( ! $ok ) {
			return $this->error( 'rbfb_db_error', __( 'The feed could not be saved to the database.', 'rewind-form-bridge-for-brevo' ), self::HTTP_SERVER_ERROR );
		}

		$feed = $this->feeds->find( $id );

		return $this->respond( null !== $feed ? $this->shape( $feed ) : array( 'id' => $id ) );
	}

	/**
	 * Delete a feed. Its log rows are deliberately kept as delivery history.
	 *
	 * @since 0.4.0
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function delete_feed( \WP_REST_Request $request ) {
		$id = (int) $request->get_param( 'id' );

		if ( null === $this->feeds->find( $id ) ) {
			return $this->error( 'rbfb_feed_not_found', __( 'That feed no longer exists.', 'rewind-form-bridge-for-brevo' ), self::HTTP_NOT_FOUND );
		}

		if ( ! $this->feeds->delete( $id ) ) {
			return $this->error( 'rbfb_db_error', __( 'The feed could not be deleted.', 'rewind-form-bridge-for-brevo' ), self::HTTP_SERVER_ERROR );
		}

		return $this->respond( array( 'deleted' => true, 'id' => $id ) );
	}

	/**
	 * Duplicate a feed, paused, so the copy cannot fire before it is checked.
	 *
	 * @since 0.4.0
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function duplicate_feed( \WP_REST_Request $request ) {
		$source = $this->feeds->find( (int) $request->get_param( 'id' ) );

		if ( null === $source ) {
			return $this->error( 'rbfb_feed_not_found', __( 'That feed no longer exists.', 'rewind-form-bridge-for-brevo' ), self::HTTP_NOT_FOUND );
		}

		$id = $this->feeds->insert(
			$source->adapter(),
			$source->form_id(),
			sprintf(
				/* translators: %s: the original feed title. */
				__( '%s (copy)', 'rewind-form-bridge-for-brevo' ),
				$source->title()
			),
			'paused',
			$source->config()
		);

		if ( 0 === $id ) {
			return $this->error( 'rbfb_db_error', __( 'The feed could not be duplicated.', 'rewind-form-bridge-for-brevo' ), self::HTTP_SERVER_ERROR );
		}

		$feed = $this->feeds->find( $id );

		return $this->respond( null !== $feed ? $this->shape( $feed ) : array( 'id' => $id ), self::HTTP_CREATED );
	}

	/**
	 * Validate a write request beyond what the args schema can express.
	 *
	 * @since 0.4.0
	 * @param \WP_REST_Request $request Request.
	 * @return array|\WP_Error Clean values, or the first failure.
	 */
	private function validate( \WP_REST_Request $request ) {
		$adapter = (string) $request->get_param( 'adapter' );
		$form_id = (string) $request->get_param( 'form_id' );
		$title   = trim( (string) $request->get_param( 'title' ) );
		$status  = (string) $request->get_param( 'status' );

		if ( null === $this->registry->get( $adapter ) ) {
			return $this->error( 'rbfb_unknown_adapter', __( 'Unknown form plugin.', 'rewind-form-bridge-for-brevo' ) );
		}

		if ( '' === $title ) {
			return $this->error( 'rbfb_missing_title', __( 'Give the feed a title.', 'rewind-form-bridge-for-brevo' ) );
		}

		if ( '' === $form_id ) {
			return $this->error( 'rbfb_missing_form', __( 'Pick a form.', 'rewind-form-bridge-for-brevo' ) );
		}

		$raw    = $request->get_param( 'config' );
		$config = $this->sanitize_config( is_array( $raw ) ? $raw : array() );

		if ( 0 === count( $config['lists'] ) ) {
			return $this->error( 'rbfb_missing_lists', __( 'Pick at least one Brevo list.', 'rewind-form-bridge-for-brevo' ) );
		}

		if ( '' === $config['email_field'] ) {
			return $this->error( 'rbfb_missing_email', __( 'Pick the form field that supplies the email address.', 'rewind-form-bridge-for-brevo' ) );
		}

		if ( '' === $config['consent_field'] && empty( $config['no_consent_ack'] ) ) {
			return $this->error( 'rbfb_missing_consent', __( 'Pick a consent checkbox field, or explicitly confirm the form needs none.', 'rewind-form-bridge-for-brevo' ) );
		}

		return array(
			'adapter' => $adapter,
			'form_id' => $form_id,
			'title'   => $title,
			'status'  => in_array( $status, array( 'active', 'paused' ), true ) ? $status : 'active',
			'config'  => $config,
		);
	}

	/**
	 * Sanitize a feed config field by field.
	 *
	 * @since 0.4.0
	 * @param array $config Raw config.
	 * @return array Clean config.
	 */
	private function sanitize_config( array $config ): array {
		$lists = array();
		if ( isset( $config['lists'] ) && is_array( $config['lists'] ) ) {
			$lists = array_values( array_unique( array_filter( array_map( 'absint', $config['lists'] ) ) ) );
		}

		$mappings = array();
		if ( isset( $config['mappings'] ) && is_array( $config['mappings'] ) ) {
			foreach ( $config['mappings'] as $mapping ) {
				if ( ! is_array( $mapping ) || empty( $mapping['field_key'] ) || empty( $mapping['attribute'] ) ) {
					continue;
				}

				$mappings[] = array(
					'field_key'   => sanitize_text_field( (string) $mapping['field_key'] ),
					'attribute'   => sanitize_text_field( (string) $mapping['attribute'] ),
					'brevo_type'  => sanitize_key( isset( $mapping['brevo_type'] ) ? (string) $mapping['brevo_type'] : 'text' ),
					'required'    => ! empty( $mapping['required'] ),
					'date_format' => sanitize_text_field( isset( $mapping['date_format'] ) ? (string) $mapping['date_format'] : '' ),
				);
			}
		}

		return array(
			'lists'           => $lists,
			'mappings'        => $mappings,
			'email_field'     => isset( $config['email_field'] ) ? sanitize_text_field( (string) $config['email_field'] ) : '',
			'update_existing' => ! empty( $config['update_existing'] ),
			'consent_field'   => isset( $config['consent_field'] ) ? sanitize_text_field( (string) $config['consent_field'] ) : '',
			'no_consent_ack'  => ! empty( $config['no_consent_ack'] ),
			'doi'             => array( 'enabled' => false ),
		);
	}

	/**
	 * Shape a Feed for the REST response, with UI-only extras joined on.
	 *
	 * @since 0.4.0
	 * @param Feed $feed Feed model.
	 * @return array
	 */
	private function shape( Feed $feed ): array {
		$adapter = $this->registry->get( $feed->adapter() );

		return array(
			'id'            => $feed->id(),
			'title'         => $feed->title(),
			'adapter'       => $feed->adapter(),
			'adapter_label' => null !== $adapter ? $adapter->label() : $feed->adapter(),
			'available'     => null !== $adapter && $adapter->is_available(),
			'form_id'       => $feed->form_id(),
			'status'        => $feed->status(),
			'config'        => $feed->config(),
			'last_sync'     => $this->logs->last_sync_for( $feed->id() ),
		);
	}
}
