<?php
/**
 * REST API - Single Event Operations.
 *
 * @package    EventKoi
 * @subpackage EventKoi\API
 */

namespace EventKoi\API;

use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use EventKoi\Core\Event as SingleEvent;
use EventKoi\API\REST;
use EKLIB\StellarWP\DB\DB;

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

/**
 * Class Event
 *
 * Handles single event-related REST API endpoints.
 */
class Event {

	/**
	 * Register REST endpoints for single event operations.
	 *
	 * @return void
	 */
	public static function init() {
		register_rest_route(
			EVENTKOI_API,
			'/event',
			array(
				'methods'             => 'GET',
				'callback'            => array( self::class, 'get_result' ),
				'permission_callback' => '__return_true',
			)
		);

		register_rest_route(
			EVENTKOI_API,
			'/get_event',
			array(
				'methods'             => 'GET',
				'callback'            => array( self::class, 'get_event' ),
				'permission_callback' => '__return_true',
			)
		);

		register_rest_route(
			EVENTKOI_API,
			'/update_event',
			array(
				'methods'             => 'POST',
				'callback'            => array( self::class, 'update_event' ),
				'permission_callback' => function () {
					return current_user_can( 'eventkoi_events_edit' ) || current_user_can( 'eventkoi_events_add' );
				},
			)
		);

		register_rest_route(
			EVENTKOI_API,
			'/restore_event',
			array(
				'methods'             => 'POST',
				'callback'            => array( self::class, 'restore_event' ),
				'permission_callback' => function () {
					return current_user_can( 'eventkoi_events_edit' );
				},
			)
		);

		register_rest_route(
			EVENTKOI_API,
			'/duplicate_event',
			array(
				'methods'             => 'POST',
				'callback'            => array( self::class, 'duplicate_event' ),
				'permission_callback' => function () {
					return current_user_can( 'eventkoi_events_add' );
				},
			)
		);

		register_rest_route(
			EVENTKOI_API,
			'/delete_event',
			array(
				'methods'             => 'POST',
				'callback'            => array( self::class, 'delete_event' ),
				'permission_callback' => function () {
					return current_user_can( 'eventkoi_events_edit' );
				},
			)
		);

		register_rest_route(
			EVENTKOI_API,
			'/instance_data',
			array(
				'methods'             => 'GET',
				'callback'            => array( self::class, 'get_instance_data' ),
				'permission_callback' => '__return_true',
			)
		);

		register_rest_route(
			EVENTKOI_API,
			'/edit_instance',
			array(
				'methods'             => 'POST',
				'callback'            => array( self::class, 'edit_instance' ),
				'permission_callback' => function () {
					return current_user_can( 'eventkoi_events_edit' );
				},
			)
		);

		register_rest_route(
			EVENTKOI_API,
			'/reset_instance',
			array(
				'methods'             => 'POST',
				'callback'            => array( self::class, 'reset_instance' ),
				'permission_callback' => function () {
					return current_user_can( 'eventkoi_events_edit' );
				},
			)
		);
	}

	/**
	 * Get a single event by ID.
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response|WP_Error The event data or error.
	 */
	public static function get_result( WP_REST_Request $request ) {
		$event_id = absint( $request->get_param( 'id' ) );

		if ( empty( $event_id ) ) {
			return new WP_Error( 'eventkoi_invalid_id', __( 'Invalid event ID.', 'eventkoi-lite' ), array( 'status' => 400 ) );
		}

		// Non-published events (draft, pending, future, private) are only
		// readable by users who can edit them. This prevents unauthenticated
		// disclosure of unpublished event data (CVE-2026-10029).
		$post = get_post( $event_id );
		if ( ! $post || 'eventkoi_event' !== $post->post_type ) {
			return new WP_Error( 'eventkoi_not_found', __( 'Event not found.', 'eventkoi-lite' ), array( 'status' => 404 ) );
		}
		if ( 'publish' !== $post->post_status && ! current_user_can( 'edit_post', $event_id ) ) {
			return new WP_Error( 'eventkoi_not_found', __( 'Event not found.', 'eventkoi-lite' ), array( 'status' => 404 ) );
		}

		$event    = new SingleEvent( $event_id );
		$response = $event::get_meta();
		$response = self::attach_rendered_event_fields( $response, $event_id );

		return rest_ensure_response( $response );
	}

	/**
	 * Retrieve a single enriched event by ID (normalized event object).
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_event( WP_REST_Request $request ) {
		$event_id = absint( $request->get_param( 'id' ) );

		$event = eventkoi_get_event( $event_id );

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

		$event = self::attach_rendered_event_fields( $event, $event_id );

		return rest_ensure_response( $event );
	}

	/**
	 * Attach rendered values for every event_* canonical field so the
	 * Gutenberg editor preview can show actual content instead of literal
	 * `{Field Name}` placeholder labels.
	 *
	 * @param array|mixed $event Single event array.
	 * @param int|null    $event_id Optional event ID; resolved from the row when omitted.
	 * @return array|mixed Enriched event (or original on bad input).
	 */
	public static function attach_rendered_event_fields( $event, $event_id = null ) {
		if ( ! is_array( $event ) ) {
			return $event;
		}

		$event_id = $event_id ? absint( $event_id ) : absint( $event['event_id'] ?? ( $event['id'] ?? 0 ) );
		if ( $event_id <= 0 ) {
			return $event;
		}

		if ( ! function_exists( 'eventkoi_get_event_data_options' ) ) {
			return $event;
		}

		$options = eventkoi_get_event_data_options();
		if ( ! is_array( $options ) ) {
			return $event;
		}

		$event_obj = new \EventKoi\Core\Event( $event_id );
		foreach ( array_keys( $options ) as $field ) {
			if ( isset( $event[ $field ] ) ) {
				continue;
			}
			$rendered = (string) $event_obj::render_meta( $field );
			if ( '' !== $rendered ) {
				$event[ $field ] = $rendered;
			}
		}

		return $event;
	}

	/**
	 * Update a single event.
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response|WP_Error The result of the update.
	 */
	public static function update_event( WP_REST_Request $request ) {
		$data = json_decode( $request->get_body(), true );

		if ( empty( $data['event'] ) || ! is_array( $data['event'] ) ) {
			return new WP_Error( 'eventkoi_invalid_event', __( 'Invalid event data.', 'eventkoi-lite' ), array( 'status' => 400 ) );
		}

		$event  = $data['event'];
		$status = isset( $data['status'] )
		? sanitize_text_field( $data['status'] )
		: ( isset( $event['wp_status'] ) ? sanitize_text_field( $event['wp_status'] ) : 'draft' );

		$event_id = absint( $event['id'] ?? 0 );

		// If no ID → create new event (auto-draft).
		if ( ! $event_id ) {
			$now     = current_time( 'mysql', false );
			$now_gmt = current_time( 'mysql', true );

			$postarr = array(
				'post_title'        => ! empty( $event['title'] ) ? sanitize_text_field( $event['title'] ) : 'Untitled event',
				'post_status'       => ! empty( $status ) ? $status : 'draft',
				'post_type'         => 'eventkoi_event',
				'post_date'         => $now,
				'post_date_gmt'     => $now_gmt,
				'post_modified'     => $now,
				'post_modified_gmt' => $now_gmt,
			);

			$new_id = wp_insert_post( $postarr, true );

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

			$query    = new SingleEvent( $new_id );
			$response = $query::get_meta();

			return rest_ensure_response( $response );
		}

		// Per-event ownership / capability check. The route-level cap is a
		// flat boolean: any holder could otherwise edit any event regardless
		// of authorship. Defer to WP's map-meta-cap so sites that grant the
		// EventKoi cap to a self-service "submitter" role retain ownership.
		if ( ! self::user_can_edit_event( $event_id ) ) {
			return new WP_Error( 'eventkoi_forbidden', __( 'You are not allowed to edit this event.', 'eventkoi-lite' ), array( 'status' => 403 ) );
		}

		$query    = new SingleEvent( $event_id );
		$response = $query::update( $event, $status );

		return rest_ensure_response( $response );
	}

	/**
	 * Restore a soft-deleted event.
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response The restoration result.
	 */
	public static function restore_event( WP_REST_Request $request ) {
		$data     = json_decode( $request->get_body(), true );
		$event_id = absint( $data['event_id'] ?? 0 );

		if ( empty( $event_id ) ) {
			return new WP_Error( 'eventkoi_missing_id', __( 'Missing event ID.', 'eventkoi-lite' ), array( 'status' => 400 ) );
		}

		if ( ! self::user_can_edit_event( $event_id ) ) {
			return new WP_Error( 'eventkoi_forbidden', __( 'You are not allowed to restore this event.', 'eventkoi-lite' ), array( 'status' => 403 ) );
		}

		$response = SingleEvent::restore_event( $event_id );

		return rest_ensure_response( $response );
	}

	/**
	 * Duplicate an event.
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response The duplication result.
	 */
	public static function duplicate_event( WP_REST_Request $request ) {
		$data     = json_decode( $request->get_body(), true );
		$event_id = absint( $data['event_id'] ?? 0 );

		if ( empty( $event_id ) ) {
			return new WP_Error( 'eventkoi_missing_id', __( 'Missing event ID.', 'eventkoi-lite' ), array( 'status' => 400 ) );
		}

		// Duplicate copies all meta from the source. user_can_read_event
		// returns true for any published event; require edit-equivalent so
		// bare-cap roles can't clone events authored by other users.
		if ( ! self::user_can_edit_event( $event_id ) ) {
			return new WP_Error( 'eventkoi_forbidden', __( 'You are not allowed to duplicate this event.', 'eventkoi-lite' ), array( 'status' => 403 ) );
		}

		$event    = new SingleEvent( $event_id );
		$response = $event::duplicate_event();

		return rest_ensure_response( $response );
	}

	/**
	 * Delete an event permanently.
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response The deletion result.
	 */
	public static function delete_event( WP_REST_Request $request ) {
		$data     = json_decode( $request->get_body(), true );
		$event_id = absint( $data['event_id'] ?? 0 );

		if ( empty( $event_id ) ) {
			return new WP_Error( 'eventkoi_missing_id', __( 'Missing event ID.', 'eventkoi-lite' ), array( 'status' => 400 ) );
		}

		if ( ! self::user_can_delete_event( $event_id ) ) {
			return new WP_Error( 'eventkoi_forbidden', __( 'You are not allowed to delete this event.', 'eventkoi-lite' ), array( 'status' => 403 ) );
		}

		$response = SingleEvent::delete_event( $event_id );

		return rest_ensure_response( $response );
	}

	/**
	 * Get instance override and merged data.
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_instance_data( WP_REST_Request $request ) {
		$event_id  = absint( $request->get_param( 'id' ) );
		$timestamp = absint( $request->get_param( 'timestamp' ) );

		if ( ! $event_id || ! $timestamp ) {
			return new \WP_Error( 'eventkoi_missing_param', __( 'Missing event ID or timestamp.', 'eventkoi-lite' ) );
		}

		// Gate unpublished instance data behind edit capability (CVE-2026-10029).
		$post = get_post( $event_id );
		if ( ! $post || 'eventkoi_event' !== $post->post_type ) {
			return new \WP_Error( 'eventkoi_not_found', __( 'Event not found.', 'eventkoi-lite' ), array( 'status' => 404 ) );
		}
		if ( 'publish' !== $post->post_status && ! current_user_can( 'edit_post', $event_id ) ) {
			return new \WP_Error( 'eventkoi_not_found', __( 'Event not found.', 'eventkoi-lite' ), array( 'status' => 404 ) );
		}

		$event = new \EventKoi\Core\Event( $event_id );
		$raw   = $event::get_meta();

		$override = eventkoi_get_instance_override( $event_id, $timestamp );
		$merged   = is_array( $override )
		? eventkoi_merge_instance_data( $raw, $override )
		: $raw;

		return rest_ensure_response(
			array(
				'event_id'  => $event_id,
				'timestamp' => $timestamp,
				'raw_event' => $raw,
				'override'  => $override,
				'merged'    => $merged,
			)
		);
	}

	/**
	 * Save override data for a specific instance.
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function edit_instance( WP_REST_Request $request ) {
		$data = json_decode( $request->get_body(), true );

		$event_id  = absint( $data['event_id'] ?? 0 );
		$timestamp = absint( $data['timestamp'] ?? 0 );
		$overrides = $data['overrides'] ?? array();
		$delete    = $data['deleteKeys'] ?? array();

		if ( ! $event_id || ! $timestamp || ! is_array( $overrides ) ) {
			return new \WP_Error(
				'eventkoi_invalid_instance_data',
				__( 'Missing or invalid instance override data.', 'eventkoi-lite' )
			);
		}

		if ( ! self::user_can_edit_event( $event_id ) ) {
			return new \WP_Error( 'eventkoi_forbidden', __( 'You are not allowed to edit this event.', 'eventkoi-lite' ), array( 'status' => 403 ) );
		}

		$existing = eventkoi_get_instance_override( $event_id, $timestamp );
		$existing = is_array( $existing ) ? $existing : array();

		$allowed_keys = array(
			'title',
			'description',
			'summary',
			'image',
			'image_id',
			'template',
			'locations',
			'modified_at',
			'status',
		);

		if ( array_key_exists( 'description', $overrides ) ) {
			$overrides['description'] = SingleEvent::sanitize_description_html( $overrides['description'] );
		}

		$filtered = array_filter(
			$overrides,
			static fn( $key ) => in_array( $key, $allowed_keys, true ),
			ARRAY_FILTER_USE_KEY
		);

		if ( empty( $filtered['modified_at'] ) ) {
			$filtered['modified_at'] = gmdate( 'c' );
		}

		$merged = array_merge( $existing, $filtered );

		if ( is_array( $delete ) ) {
			foreach ( $delete as $key ) {
				unset( $merged[ $key ] );
			}
		}

		// Upsert into custom table.
		DB::table( 'eventkoi_recurrence_overrides' )->upsert(
			array(
				'event_id'    => $event_id,
				'timestamp'   => $timestamp,
				'data'        => maybe_serialize( $merged ),
				'modified_at' => current_time( 'mysql', true ),
			),
			array( 'event_id', 'timestamp' )
		);

		return rest_ensure_response(
			array(
				'success'   => true,
				'event_id'  => $event_id,
				'timestamp' => $timestamp,
				'override'  => $merged,
			)
		);
	}

	/**
	 * Remove all override data for a specific instance.
	 *
	 * @param WP_REST_Request $request The REST request.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function reset_instance( WP_REST_Request $request ) {
		$data      = json_decode( $request->get_body(), true );
		$event_id  = absint( $data['event_id'] ?? 0 );
		$timestamp = absint( $data['timestamp'] ?? 0 );

		if ( ! $event_id || ! $timestamp ) {
			return new \WP_Error( 'eventkoi_invalid_reset', __( 'Missing event ID or timestamp.', 'eventkoi-lite' ) );
		}

		DB::table( 'eventkoi_recurrence_overrides' )
		->where( 'event_id', $event_id )
		->where( 'timestamp', $timestamp )
		->delete();

		return rest_ensure_response(
			array(
				'success'   => true,
				'event_id'  => $event_id,
				'timestamp' => $timestamp,
				'message'   => __( 'Instance overrides reset to defaults.', 'eventkoi-lite' ),
			)
		);
	}

	/**
	 * Per-event "can edit" check. Doesn't rely on the CPT's capability_type=post
	 * chain because custom EventKoi-only roles (Submitter) typically only hold
	 * eventkoi_events_edit, not edit_others_posts. Allows authors to mutate
	 * their own events; users with WP's edit_others_posts or manage_options
	 * can edit anything (preserves admin/editor flows).
	 *
	 * @param int $event_id Event ID.
	 * @return bool
	 */
	private static function user_can_edit_event( $event_id ) {
		$event_id = absint( $event_id );
		if ( ! $event_id ) {
			return false;
		}
		if ( current_user_can( 'edit_others_posts' ) || current_user_can( 'manage_options' ) ) {
			return true;
		}
		$author_id = (int) get_post_field( 'post_author', $event_id );
		$user_id   = get_current_user_id();
		return $user_id > 0 && $author_id === $user_id;
	}

	/**
	 * Per-event "can read" check — looser than edit: published events readable
	 * to anyone, otherwise edit-equivalent.
	 *
	 * @param int $event_id Event ID.
	 * @return bool
	 */
	private static function user_can_read_event( $event_id ) {
		$event_id = absint( $event_id );
		if ( ! $event_id ) {
			return false;
		}
		$post = get_post( $event_id );
		if ( ! $post ) {
			return false;
		}
		if ( 'publish' === $post->post_status ) {
			return true;
		}
		return self::user_can_edit_event( $event_id );
	}

	/**
	 * Per-event "can delete" check. Mirrors edit boundary.
	 *
	 * @param int $event_id Event ID.
	 * @return bool
	 */
	private static function user_can_delete_event( $event_id ) {
		if ( current_user_can( 'delete_others_posts' ) || current_user_can( 'manage_options' ) ) {
			return true;
		}
		return self::user_can_edit_event( $event_id );
	}
}
