<?php
/**
 * REST API Controller for Layouts.
 *
 * @package OpusPriceBuilder\API
 */

declare(strict_types=1);

namespace OpusPriceBuilder\API;

use OpusPriceBuilder\Database\LayoutsTable;
use OpusPriceBuilder\Export\CanvasSanitizer;
use OpusPriceBuilder\Locations;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_Error;

/**
 * REST API controller for managing price layouts.
 */
class LayoutsController extends WP_REST_Controller {

	/**
	 * Namespace for the REST routes.
	 *
	 * @var string
	 */
	protected $namespace = 'opus-price-builder/v1';

	/**
	 * Resource name.
	 *
	 * @var string
	 */
	protected $rest_base = 'layouts';

	/**
	 * Register the routes for layouts.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		// GET /layouts - List all layouts
		// POST /layouts - Create a new layout
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		// GET /layouts/{id} - Get single layout
		// PUT/PATCH /layouts/{id} - Update layout
		// DELETE /layouts/{id} - Delete layout
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'   => array(
					'id' => array(
						'description' => __( 'Unique identifier for the layout.', 'opus-price-builder' ),
						'type'        => 'integer',
						'required'    => true,
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		// POST /layouts/{id}/duplicate - Duplicate a layout
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/duplicate',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'duplicate_item' ),
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
				'args'                => array(
					'id' => array(
						'description' => __( 'Layout ID to duplicate.', 'opus-price-builder' ),
						'type'        => 'integer',
						'required'    => true,
					),
				),
			)
		);

		// PUT /layouts/{id}/activate-location - Activate/deactivate for a specific location
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/activate-location',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'activate_location' ),
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
				'args'                => array(
					'id'       => array(
						'description' => __( 'Layout ID to activate/deactivate.', 'opus-price-builder' ),
						'type'        => 'integer',
						'required'    => true,
					),
					'location' => array(
						'description' => __( 'Location to activate for.', 'opus-price-builder' ),
						'type'        => 'string',
						'required'    => true,
						// Validation done in callback since locations are dynamic.
					),
					'active'   => array(
						'description' => __( 'Whether to activate or deactivate.', 'opus-price-builder' ),
						'type'        => 'boolean',
						'default'     => true,
					),
				),
			)
		);

		// GET /layouts/locations - Get available locations
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/locations',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_locations' ),
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
			)
		);

		// GET /layouts/active - Get all active layouts for all scenarios and locations
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/active',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_active_layouts' ),
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
			)
		);
	}

	/**
	 * Check if a given request has access to get items.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ): bool|WP_Error {
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return new WP_Error(
				'rest_forbidden',
				__( 'You do not have permission to view layouts.', 'opus-price-builder' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}
		return true;
	}

	/**
	 * Check if a given request has access to get a specific item.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ): bool|WP_Error {
		return $this->get_items_permissions_check( $request );
	}

	/**
	 * Check if a given request has access to create items.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ): bool|WP_Error {
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return new WP_Error(
				'rest_forbidden',
				__( 'You do not have permission to create layouts.', 'opus-price-builder' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}
		return true;
	}

	/**
	 * Check if a given request has access to update a specific item.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has access to update the item, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ): bool|WP_Error {
		return $this->create_item_permissions_check( $request );
	}

	/**
	 * Check if a given request has access to delete a specific item.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ): bool|WP_Error {
		return $this->create_item_permissions_check( $request );
	}

	/**
	 * Retrieve layouts.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ): WP_REST_Response|WP_Error {
		$args = array(
			'per_page' => $request->get_param( 'per_page' ) ?? 20,
			'page'     => $request->get_param( 'page' ) ?? 1,
			'orderby'  => $request->get_param( 'orderby' ) ?? 'created_at',
			'order'    => $request->get_param( 'order' ) ?? 'DESC',
		);

		// Filter by scenario.
		$scenario = $request->get_param( 'scenario' );
		if ( $scenario ) {
			$args['scenario'] = $scenario;
		}

		// Filter by status.
		$status = $request->get_param( 'status' );
		if ( $status ) {
			$args['status'] = $status;
		}

		// Search by name.
		$search = $request->get_param( 'search' );
		if ( $search ) {
			$args['search'] = $search;
		}

		$layouts = LayoutsTable::getAll( $args );
		$total   = LayoutsTable::count( $args );

		$response = array();
		foreach ( $layouts as $layout ) {
			$response[] = $this->prepare_item_for_response( $layout, $request )->get_data();
		}

		$rest_response = new WP_REST_Response( $response, 200 );

		// Add pagination headers.
		$max_pages = ceil( $total / $args['per_page'] );
		$rest_response->header( 'X-WP-Total', (string) $total );
		$rest_response->header( 'X-WP-TotalPages', (string) $max_pages );

		return $rest_response;
	}

	/**
	 * Retrieve a single layout.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ): WP_REST_Response|WP_Error {
		$id     = (int) $request->get_param( 'id' );
		$layout = LayoutsTable::getById( $id );

		if ( ! $layout ) {
			return new WP_Error(
				'rest_layout_not_found',
				__( 'Layout not found.', 'opus-price-builder' ),
				array( 'status' => 404 )
			);
		}

		return $this->prepare_item_for_response( $layout, $request );
	}

	/**
	 * Create a layout.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ): WP_REST_Response|WP_Error {
		$data = array(
			'name'        => $request->get_param( 'name' ) ?? '',
			'slug'        => $request->get_param( 'slug' ) ?? '',
			'scenario'    => $request->get_param( 'scenario' ) ?? 'simple',
			'canvas_data' => $this->sanitizeCanvasDataParam( $request->get_param( 'canvas_data' ) ?? '{}' ),
		);

		// Validate required fields.
		if ( empty( $data['name'] ) ) {
			return new WP_Error(
				'rest_missing_name',
				__( 'Layout name is required.', 'opus-price-builder' ),
				array( 'status' => 400 )
			);
		}

		// Convert JSON data if needed.
		if ( is_array( $data['canvas_data'] ) ) {
			$data['canvas_data'] = wp_json_encode( $data['canvas_data'] );
		}

		// Handle active_locations as flexible JSON.
		$active_locations = $request->get_param( 'active_locations' );
		if ( null !== $active_locations && is_array( $active_locations ) ) {
			// Build locations object with only valid locations.
			$locations_data      = array();
			$available_locations = LayoutsTable::getAvailableLocations();
			foreach ( $available_locations as $location_key => $location_label ) {
				$locations_data[ $location_key ] = isset( $active_locations[ $location_key ] )
					? (bool) $active_locations[ $location_key ]
					: false;
			}
			$data['active_locations'] = wp_json_encode( $locations_data );
		}

		$id = LayoutsTable::insert( $data );

		if ( ! $id ) {
			return new WP_Error(
				'rest_layout_create_failed',
				__( 'Failed to create layout.', 'opus-price-builder' ),
				array( 'status' => 500 )
			);
		}

		// Handle exclusive location activations per scenario+location.
		if ( null !== $active_locations && is_array( $active_locations ) ) {
			foreach ( $active_locations as $location => $is_active ) {
				if ( is_string( $location ) && LayoutsTable::isValidLocation( $location ) && $is_active ) {
					LayoutsTable::setActiveForLocation( $id, $location, true );
				}
			}
		}

		$layout = LayoutsTable::getById( $id );

		$response = $this->prepare_item_for_response( $layout, $request );
		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );

		return $response;
	}

	/**
	 * Update a layout.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ): WP_REST_Response|WP_Error {
		$id     = (int) $request->get_param( 'id' );
		$layout = LayoutsTable::getById( $id );

		if ( ! $layout ) {
			return new WP_Error(
				'rest_layout_not_found',
				__( 'Layout not found.', 'opus-price-builder' ),
				array( 'status' => 404 )
			);
		}

		$data = array();

		// Only update provided fields.
		$fields = array(
			'name',
			'slug',
			'scenario',
			'canvas_data',
		);

		foreach ( $fields as $field ) {
			$value = $request->get_param( $field );
			if ( null !== $value ) {
				if ( 'canvas_data' === $field ) {
					$data[ $field ] = $this->sanitizeCanvasDataParam( $value );
				} else {
					$data[ $field ] = $value;
				}
			}
		}

		// Convert JSON data if needed.
		if ( isset( $data['canvas_data'] ) && is_array( $data['canvas_data'] ) ) {
			$data['canvas_data'] = wp_json_encode( $data['canvas_data'] );
		}

		// Handle active_locations as flexible JSON.
		$active_locations = $request->get_param( 'active_locations' );
		if ( null !== $active_locations && is_array( $active_locations ) ) {
			// Build locations object with only valid locations.
			$locations_data      = array();
			$available_locations = LayoutsTable::getAvailableLocations();
			foreach ( $available_locations as $location_key => $location_label ) {
				$locations_data[ $location_key ] = isset( $active_locations[ $location_key ] )
					? (bool) $active_locations[ $location_key ]
					: false;
			}
			$data['active_locations'] = wp_json_encode( $locations_data );
		}

		$updated = LayoutsTable::update( $id, $data );

		if ( ! $updated ) {
			return new WP_Error(
				'rest_layout_update_failed',
				__( 'Failed to update layout.', 'opus-price-builder' ),
				array( 'status' => 500 )
			);
		}

		// Handle exclusive location activations per scenario+location.
		if ( null !== $active_locations && is_array( $active_locations ) ) {
			foreach ( $active_locations as $location => $is_active ) {
				if ( is_string( $location ) && LayoutsTable::isValidLocation( $location ) && $is_active ) {
					LayoutsTable::setActiveForLocation( $id, $location, true );
				}
			}
		}

		$layout = LayoutsTable::getById( $id );
		return $this->prepare_item_for_response( $layout, $request );
	}

	/**
	 * Delete a layout.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ): WP_REST_Response|WP_Error {
		$id     = (int) $request->get_param( 'id' );
		$layout = LayoutsTable::getById( $id );

		if ( ! $layout ) {
			return new WP_Error(
				'rest_layout_not_found',
				__( 'Layout not found.', 'opus-price-builder' ),
				array( 'status' => 404 )
			);
		}

		$deleted = LayoutsTable::delete( $id );

		if ( ! $deleted ) {
			return new WP_Error(
				'rest_layout_delete_failed',
				__( 'Failed to delete layout.', 'opus-price-builder' ),
				array( 'status' => 500 )
			);
		}

		return new WP_REST_Response(
			array(
				'deleted' => true,
				'id'      => $id,
			),
			200
		);
	}

	/**
	 * Duplicate a layout.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function duplicate_item( $request ): WP_REST_Response|WP_Error {
		$id     = (int) $request->get_param( 'id' );
		$layout = LayoutsTable::getById( $id );

		if ( ! $layout ) {
			return new WP_Error(
				'rest_layout_not_found',
				__( 'Layout not found.', 'opus-price-builder' ),
				array( 'status' => 404 )
			);
		}

		$new_id = LayoutsTable::duplicate( $id );

		if ( ! $new_id ) {
			return new WP_Error(
				'rest_layout_duplicate_failed',
				__( 'Failed to duplicate layout.', 'opus-price-builder' ),
				array( 'status' => 500 )
			);
		}

		$new_layout = LayoutsTable::getById( $new_id );
		$response   = $this->prepare_item_for_response( $new_layout, $request );
		$response->set_status( 201 );

		return $response;
	}

	/**
	 * Activate or deactivate a layout for a specific location.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function activate_location( $request ): WP_REST_Response|WP_Error {
		$id       = (int) $request->get_param( 'id' );
		$location = $request->get_param( 'location' );
		$active   = $request->get_param( 'active' ) ?? true;

		// Validate location.
		if ( ! LayoutsTable::isValidLocation( $location ) ) {
			return new WP_Error(
				'rest_invalid_location',
				__( 'Invalid location specified.', 'opus-price-builder' ),
				array( 'status' => 400 )
			);
		}

		$layout = LayoutsTable::getById( $id );

		if ( ! $layout ) {
			return new WP_Error(
				'rest_layout_not_found',
				__( 'Layout not found.', 'opus-price-builder' ),
				array( 'status' => 404 )
			);
		}

		$updated = LayoutsTable::setActiveForLocation( $id, $location, (bool) $active );

		if ( ! $updated ) {
			return new WP_Error(
				'rest_layout_activate_failed',
				__( 'Failed to update layout activation.', 'opus-price-builder' ),
				array( 'status' => 500 )
			);
		}

		// Refresh layout data after update.
		$layout = LayoutsTable::getById( $id );
		return $this->prepare_item_for_response( $layout, $request );
	}

	/**
	 * Get available locations for layouts.
	 *
	 * Returns a simple key => label map for backward compatibility with React.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Response object.
	 */
	public function get_locations( $request ): WP_REST_Response {
		// Return simple key => label format for React compatibility.
		$locations = LayoutsTable::getAvailableLocations();

		return new WP_REST_Response( $locations, 200 );
	}

	/**
	 * Get all active layouts for all scenarios and locations.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Response object.
	 */
	public function get_active_layouts( $request ): WP_REST_Response {
		$all_active = LayoutsTable::getAllActiveLayouts();

		$response = array();
		foreach ( $all_active as $scenario => $locations ) {
			$response[ $scenario ] = array();
			foreach ( $locations as $location => $layout ) {
				$response[ $scenario ][ $location ] = $layout ? $this->prepare_item_for_response( $layout, $request )->get_data() : null;
			}
		}

		return new WP_REST_Response( $response, 200 );
	}

	/**
	 * Prepare the item for the REST response.
	 *
	 * @param object          $item    Layout object from database.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ): WP_REST_Response {
		// Parse active_locations from JSON column.
		$active_locations = array();
		if ( ! empty( $item->active_locations ) ) {
			$decoded = json_decode( $item->active_locations, true );
			if ( is_array( $decoded ) ) {
				$active_locations = $decoded;
			}
		}

		$data = array(
			'id'              => (int) $item->id,
			'name'            => $item->name,
			'slug'            => $item->slug,
			'scenario'        => $item->scenario,
			'activeLocations' => $active_locations,
			'canvasData'      => json_decode( $item->canvas_data, true ) ?: array(),
			'createdAt'       => $item->created_at,
			'updatedAt'       => $item->updated_at,
		);

		return new WP_REST_Response( $data, 200 );
	}

	/**
	 * Get the layout schema, conforming to JSON Schema.
	 *
	 * @return array<string, mixed> Item schema data.
	 */
	public function get_item_schema(): array {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'layout',
			'type'       => 'object',
			'properties' => array(
				'id'               => array(
					'description' => __( 'Unique identifier for the layout.', 'opus-price-builder' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'name'             => array(
					'description' => __( 'The name of the layout.', 'opus-price-builder' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'required'    => true,
				),
				'slug'             => array(
					'description' => __( 'An alphanumeric identifier for the layout.', 'opus-price-builder' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
				),
				'scenario'         => array(
					'description' => __( 'Price scenario this layout is for.', 'opus-price-builder' ),
					'type'        => 'string',
					'enum'        => array( 'simple', 'sale', 'variable_simple', 'variable_sale' ),
					'default'     => 'simple',
					'context'     => array( 'view', 'edit' ),
				),
				'active_locations' => array(
					'description' => __( 'Location-based activation settings as JSON object.', 'opus-price-builder' ),
					'type'        => 'object',
					'context'     => array( 'view', 'edit' ),
				),
				'canvas_data'      => array(
					'description' => __( 'JSON data for canvas elements.', 'opus-price-builder' ),
					'type'        => array( 'object', 'string' ),
					'context'     => array( 'view', 'edit' ),
				),
		
				'created_at'       => array(
					'description' => __( 'The date the layout was created.', 'opus-price-builder' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view' ),
					'readonly'    => true,
				),
				'updated_at'       => array(
					'description' => __( 'The date the layout was last updated.', 'opus-price-builder' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view' ),
					'readonly'    => true,
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Get the query params for collections.
	 *
	 * @return array<string, mixed> Collection parameters.
	 */
	public function get_collection_params(): array {
		return array(
			'page'     => array(
				'description'       => __( 'Current page of the collection.', 'opus-price-builder' ),
				'type'              => 'integer',
				'default'           => 1,
				'sanitize_callback' => 'absint',
				'minimum'           => 1,
			),
			'per_page' => array(
				'description'       => __( 'Maximum number of items to be returned per page.', 'opus-price-builder' ),
				'type'              => 'integer',
				'default'           => 20,
				'sanitize_callback' => 'absint',
				'minimum'           => 1,
				'maximum'           => 100,
			),
			'search'   => array(
				'description'       => __( 'Limit results to those matching a string.', 'opus-price-builder' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'scenario' => array(
				'description' => __( 'Limit results to a specific scenario.', 'opus-price-builder' ),
				'type'        => 'string',
				'enum'        => array( 'simple', 'sale', 'variable_simple', 'variable_sale' ),
			),
			'orderby'  => array(
				'description' => __( 'Sort collection by attribute.', 'opus-price-builder' ),
				'type'        => 'string',
				'default'     => 'created_at',
				'enum'        => array( 'id', 'name', 'created_at', 'updated_at' ),
			),
			'order'    => array(
				'description' => __( 'Order sort attribute ascending or descending.', 'opus-price-builder' ),
				'type'        => 'string',
				'default'     => 'DESC',
				'enum'        => array( 'ASC', 'DESC' ),
			),
		);
	}

	/**
	 * Sanitize canvas data before persisting — strips user-supplied code fields.
	 *
	 * @param mixed $canvas_data Raw canvas data from REST request.
	 * @return array<string, mixed>|string
	 */
	private function sanitizeCanvasDataParam( $canvas_data ) {
		if ( is_string( $canvas_data ) ) {
			$decoded = json_decode( $canvas_data, true );
			if ( ! is_array( $decoded ) ) {
				return $canvas_data;
			}
			$canvas_data = $decoded;
		}

		if ( ! is_array( $canvas_data ) ) {
			return array();
		}

		return CanvasSanitizer::sanitize( $canvas_data );
	}
}
