<?php
/**
 * REST API controller.
 *
 * @package CatalogFrameImageStudio
 */

namespace CatalogFrame_Image_Studio;

use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

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

/**
 * Handles provider-neutral image generation through the WordPress AI Client.
 */
final class Rest_Controller {
	const NAMESPACE         = 'catalogframe/v1';
	const JOB_HOOK          = 'cfis_process_generation_job';
	const JOB_OPTION_PREFIX = 'cfis_generation_job_';

	/**
	 * Product mapper.
	 *
	 * @var Product_Mapper
	 */
	private $product_mapper;

	/**
	 * Constructor.
	 *
	 * @param Product_Mapper $product_mapper Product mapper.
	 */
	public function __construct( Product_Mapper $product_mapper ) {
		$this->product_mapper = $product_mapper;
	}

	/**
	 * Registers hooks.
	 *
	 * @return void
	 */
	public function init() {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
		add_action( self::JOB_HOOK, array( $this, 'process_generation_job' ), 10, 1 );
	}

	/**
	 * Registers REST routes.
	 *
	 * @return void
	 */
	public function register_routes() {
		register_rest_route(
			self::NAMESPACE,
			'/status',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_status' ),
				'permission_callback' => array( $this, 'can_manage' ),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/generate',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'create_generation_job' ),
				'permission_callback' => array( $this, 'can_manage' ),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/generate/(?P<job_id>[a-f0-9-]+)',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_generation_job' ),
				'permission_callback' => array( $this, 'can_manage' ),
			)
		);
	}

	/**
	 * Capability check.
	 *
	 * @return bool
	 */
	public function can_manage() {
		return current_user_can( CFIS_MANAGE_CAPABILITY );
	}

	/**
	 * Returns plugin status for the admin app.
	 *
	 * @return WP_REST_Response
	 */
	public function get_status() {
		return rest_ensure_response(
			array(
				'aiClientAvailable'        => function_exists( 'wp_ai_client_prompt' ),
				'imageGenerationAvailable' => $this->is_image_generation_available(),
				'canConfigureProviders'    => current_user_can( 'manage_options' ),
				'wooAvailable'              => $this->product_mapper->is_available(),
			)
		);
	}

	/**
	 * Creates a background generation job and returns immediately.
	 *
	 * @param WP_REST_Request $request REST request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function create_generation_job( WP_REST_Request $request ) {
		if ( ! $this->is_image_generation_available() ) {
			return $this->image_generation_unavailable_error();
		}

		$payload = $this->sanitize_generation_payload( $request );

		if ( '' === $payload['sku'] || '' === $payload['productName'] || '' === $payload['prompt'] ) {
			return new WP_Error( 'cfis_missing_row_data', __( 'SKU, product name, and prompt are required.', 'catalogframe-image-studio' ), array( 'status' => 400 ) );
		}

		$job_id = wp_generate_uuid4();
		$job    = array(
			'id'        => $job_id,
			'status'    => 'queued',
			'createdAt' => time(),
			'updatedAt' => time(),
			'payload'   => $payload,
			'result'    => null,
			'error'     => '',
		);

		$this->save_generation_job( $job );

		if ( ! wp_next_scheduled( self::JOB_HOOK, array( $job_id ) ) ) {
			wp_schedule_single_event( time(), self::JOB_HOOK, array( $job_id ) );
		}

		if ( function_exists( 'spawn_cron' ) ) {
			spawn_cron( time() );
		}

		return rest_ensure_response( $this->public_generation_job( $job ) );
	}

	/**
	 * Returns a generation job status.
	 *
	 * @param WP_REST_Request $request REST request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_generation_job( WP_REST_Request $request ) {
		$job_id = sanitize_text_field( (string) $request->get_param( 'job_id' ) );
		$job    = $this->get_stored_generation_job( $job_id );

		if ( ! is_array( $job ) ) {
			return new WP_Error( 'cfis_generation_job_missing', __( 'Generation job was not found.', 'catalogframe-image-studio' ), array( 'status' => 404 ) );
		}

		if ( 'queued' === $job['status'] && ! wp_next_scheduled( self::JOB_HOOK, array( $job_id ) ) ) {
			wp_schedule_single_event( time(), self::JOB_HOOK, array( $job_id ) );
			if ( function_exists( 'spawn_cron' ) ) {
				spawn_cron( time() );
			}
		}

		return rest_ensure_response( $this->public_generation_job( $job ) );
	}

	/**
	 * Processes a queued generation job outside the browser request.
	 *
	 * @param string $job_id Job ID.
	 * @return void
	 */
	public function process_generation_job( $job_id ) {
		$job = $this->get_stored_generation_job( (string) $job_id );
		if ( ! is_array( $job ) || 'queued' !== $job['status'] ) {
			return;
		}

		$job['status']    = 'running';
		$job['updatedAt'] = time();
		$this->save_generation_job( $job );

		if ( function_exists( 'ignore_user_abort' ) ) {
			ignore_user_abort( true );
		}

		$previous_user_id = get_current_user_id();
		if ( ! empty( $job['payload']['userId'] ) ) {
			wp_set_current_user( (int) $job['payload']['userId'] );
		}

		$result = $this->generate_image_from_payload( $job['payload'] );

		wp_set_current_user( $previous_user_id );

		if ( is_wp_error( $result ) ) {
			$job['status'] = 'failed';
			$job['error']  = $result->get_error_message();
		} else {
			$job['status'] = 'completed';
			$job['result'] = $result;
		}

		$job['updatedAt'] = time();
		$this->save_generation_job( $job );
	}

	/**
	 * Generates one product image, saves it to Media Library, and optionally maps it.
	 *
	 * @param array $payload Sanitized generation payload.
	 * @return array|WP_Error
	 */
	private function generate_image_from_payload( array $payload ) {
		if ( ! $this->is_image_generation_available() ) {
			return $this->image_generation_unavailable_error();
		}

		$sku             = $payload['sku'];
		$product_name    = $payload['productName'];
		$prompt          = $payload['prompt'];
		$auto_map        = (bool) $payload['autoMap'];
		$options         = $payload['options'];
		$request_options = \WordPress\AiClient\Providers\Http\DTO\RequestOptions::fromArray(
			array(
				\WordPress\AiClient\Providers\Http\DTO\RequestOptions::KEY_TIMEOUT => 240.0,
			)
		);
		$builder = wp_ai_client_prompt( $prompt )
			->using_request_options( $request_options );

		if ( ! empty( $payload['referenceImage'] ) ) {
			$builder->with_text( 'The next attached reference image depicts the product. Use it to preserve the product\'s visual identity, shape, materials, and distinguishing details.' );
			$builder->with_file( $payload['referenceImage'] );
		}

		if ( ! empty( $payload['brandLogo'] ) ) {
			$builder->with_text( 'The next attached reference image is the brand logo. Use it as the brand logo in the generated image, preserving its wording, symbol, proportions, and colors faithfully. Do not invent, replace, or redesign it.' );
			$builder->with_file( $payload['brandLogo'] );
		}

		$aspect_ratio = $this->aspect_ratio_for_size( $options['size'] );
		if ( '' !== $aspect_ratio ) {
			$builder->as_output_media_aspect_ratio( $aspect_ratio );
		}

		if ( ! $builder->is_supported_for_image_generation() ) {
			return $this->image_generation_unavailable_error();
		}

		$image_file = $builder->generate_image();
		if ( is_wp_error( $image_file ) ) {
			return $image_file;
		}

		$image = $this->read_generated_image( $image_file );
		if ( is_wp_error( $image ) ) {
			return $image;
		}

		$attachment = $this->save_media_attachment( $image['bytes'], $sku, $product_name, $prompt, $image['mimeType'] );
		if ( is_wp_error( $attachment ) ) {
			return $attachment;
		}

		$mapping = array(
			'mapped'  => false,
			'message' => __( 'Auto-mapping was not enabled.', 'catalogframe-image-studio' ),
		);

		if ( $auto_map ) {
			$mapping = $this->product_mapper->append_attachment_by_sku( $sku, (int) $attachment['id'] );
		}

		return array(
			'sku'        => $sku,
			'attachment' => $attachment,
			'mapping'    => $mapping,
		);
	}

	/**
	 * Sanitizes generation request data into a background-job payload.
	 *
	 * @param WP_REST_Request $request REST request.
	 * @return array
	 */
	private function sanitize_generation_payload( WP_REST_Request $request ) {
		$options = is_array( $request->get_param( 'options' ) ) ? $request->get_param( 'options' ) : array();
		return array(
			'sku'            => sanitize_text_field( (string) $request->get_param( 'sku' ) ),
			'productName'    => sanitize_text_field( (string) $request->get_param( 'productName' ) ),
			'prompt'         => trim( wp_kses_post( (string) $request->get_param( 'prompt' ) ) ),
			'referenceImage' => esc_url_raw( (string) $request->get_param( 'referenceImage' ), array( 'http', 'https' ) ),
			'brandLogo'      => esc_url_raw( (string) $request->get_param( 'brandLogo' ), array( 'http', 'https' ) ),
			'autoMap'        => rest_sanitize_boolean( $request->get_param( 'autoMap' ) ),
			'userId'         => get_current_user_id(),
			'options'        => array(
				'size' => $this->sanitize_choice( isset( $options['size'] ) ? $options['size'] : '1024x1024', array( 'auto', '1024x1024', '1536x1024', '1024x1536' ), '1024x1024' ),
			),
		);
	}

	/**
	 * Checks whether an installed, configured provider can generate images.
	 *
	 * @return bool
	 */
	private function is_image_generation_available() {
		if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
			return false;
		}

		$builder = wp_ai_client_prompt( 'CatalogFrame image capability check.' );
		return (bool) $builder->is_supported_for_image_generation();
	}

	/**
	 * Returns the actionable error used when image generation is unavailable.
	 *
	 * @return WP_Error
	 */
	private function image_generation_unavailable_error() {
		if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
			return new WP_Error(
				'cfis_ai_client_unavailable',
				__( 'The WordPress AI Client is unavailable. CatalogFrame requires WordPress 7.0 or newer.', 'catalogframe-image-studio' ),
				array( 'status' => 503 )
			);
		}

		return new WP_Error(
			'cfis_image_generation_unavailable',
			__( 'No configured AI provider supports this image request. Ask a site administrator to install and configure an image-capable provider in Settings > Connectors.', 'catalogframe-image-studio' ),
			array( 'status' => 503 )
		);
	}

	/**
	 * Maps the legacy size preference to the provider-neutral aspect ratio contract.
	 *
	 * @param string $size Sanitized size preference.
	 * @return string
	 */
	private function aspect_ratio_for_size( $size ) {
		$ratios = array(
			'1024x1024' => '1:1',
			'1536x1024' => '3:2',
			'1024x1536' => '2:3',
		);

		return isset( $ratios[ $size ] ) ? $ratios[ $size ] : '';
	}

	/**
	 * Reads and validates a generated raster image returned by the AI Client.
	 *
	 * @param object $image_file WordPress AI Client File DTO.
	 * @return array|WP_Error
	 */
	private function read_generated_image( $image_file ) {
		if ( ! is_object( $image_file ) || ! method_exists( $image_file, 'isImage' ) || ! $image_file->isImage() ) {
			return new WP_Error( 'cfis_generation_not_image', __( 'The configured AI provider did not return an image.', 'catalogframe-image-studio' ), array( 'status' => 502 ) );
		}

		$mime_type = method_exists( $image_file, 'getMimeType' ) ? (string) $image_file->getMimeType() : '';
		$mime_type = $this->sanitize_generated_mime_type( $mime_type );
		if ( '' === $mime_type ) {
			return new WP_Error( 'cfis_generation_unsupported_type', __( 'The configured AI provider returned an unsupported image format.', 'catalogframe-image-studio' ), array( 'status' => 502 ) );
		}

		$base64_data = method_exists( $image_file, 'getBase64Data' ) ? (string) $image_file->getBase64Data() : '';
		if ( '' !== $base64_data ) {
			$bytes = base64_decode( $base64_data, true );
			if ( false === $bytes || '' === $bytes ) {
				return new WP_Error( 'cfis_generation_decode_failed', __( 'Generated image data could not be decoded.', 'catalogframe-image-studio' ), array( 'status' => 502 ) );
			}

			return array(
				'bytes'    => $bytes,
				'mimeType' => $mime_type,
			);
		}

		$url = method_exists( $image_file, 'getUrl' ) ? (string) $image_file->getUrl() : '';
		if ( '' === $url ) {
			return new WP_Error( 'cfis_generation_empty', __( 'The configured AI provider did not return image data.', 'catalogframe-image-studio' ), array( 'status' => 502 ) );
		}

		$response = wp_safe_remote_get( $url, array( 'timeout' => 240 ) );
		if ( is_wp_error( $response ) ) {
			return new WP_Error( 'cfis_generation_download_failed', $response->get_error_message(), array( 'status' => 502 ) );
		}

		$code  = (int) wp_remote_retrieve_response_code( $response );
		$bytes = wp_remote_retrieve_body( $response );
		if ( $code < 200 || $code >= 300 || '' === $bytes ) {
			return new WP_Error( 'cfis_generation_download_failed', __( 'WordPress could not download the generated image returned by the configured AI provider.', 'catalogframe-image-studio' ), array( 'status' => 502 ) );
		}

		return array(
			'bytes'    => $bytes,
			'mimeType' => $mime_type,
		);
	}

	/**
	 * Allows only raster formats WordPress can safely store as generated media.
	 *
	 * @param string $mime_type Provider-returned MIME type.
	 * @return string
	 */
	private function sanitize_generated_mime_type( $mime_type ) {
		$allowed   = array( 'image/png', 'image/jpeg', 'image/webp', 'image/avif' );
		$mime_type = strtolower( trim( (string) $mime_type ) );

		return in_array( $mime_type, $allowed, true ) ? $mime_type : '';
	}

	/**
	 * Saves a generation job.
	 *
	 * @param array $job Job data.
	 * @return void
	 */
	private function save_generation_job( array $job ) {
		update_option( $this->generation_job_option_name( (string) $job['id'] ), $job, false );
	}

	/**
	 * Reads a generation job.
	 *
	 * @param string $job_id Job ID.
	 * @return array|null
	 */
	private function get_stored_generation_job( $job_id ) {
		$stored = get_option( $this->generation_job_option_name( $job_id ), null );
		return is_array( $stored ) ? $stored : null;
	}

	/**
	 * Builds a generation job option name.
	 *
	 * @param string $job_id Job ID.
	 * @return string
	 */
	private function generation_job_option_name( $job_id ) {
		return self::JOB_OPTION_PREFIX . sanitize_key( $job_id );
	}

	/**
	 * Returns public job fields for the admin UI.
	 *
	 * @param array $job Job data.
	 * @return array
	 */
	private function public_generation_job( array $job ) {
		return array(
			'jobId'     => $job['id'],
			'status'    => $job['status'],
			'createdAt' => $job['createdAt'],
			'updatedAt' => $job['updatedAt'],
			'result'    => $job['result'],
			'error'     => $job['error'],
		);
	}

	/**
	 * Saves generated bytes to the Media Library.
	 *
	 * @param string $bytes        Image bytes.
	 * @param string $sku          Product SKU.
	 * @param string $product_name Product name.
	 * @param string $prompt       Prompt.
	 * @param string $mime_type    Validated image MIME type.
	 * @return array|WP_Error
	 */
	private function save_media_attachment( $bytes, $sku, $product_name, $prompt, $mime_type ) {
		if ( ! current_user_can( 'upload_files' ) ) {
			return new WP_Error( 'cfis_upload_denied', __( 'You do not have permission to upload generated images.', 'catalogframe-image-studio' ), array( 'status' => 403 ) );
		}

		$extensions = array(
			'image/png'  => 'png',
			'image/jpeg' => 'jpg',
			'image/webp' => 'webp',
			'image/avif' => 'avif',
		);
		$extension = $extensions[ $mime_type ];
		$filename  = sanitize_file_name( $sku . '-' . gmdate( 'Ymd-His' ) . '.' . $extension );
		$upload    = wp_upload_bits( $filename, null, $bytes );

		if ( ! empty( $upload['error'] ) ) {
			return new WP_Error( 'cfis_upload_failed', $upload['error'], array( 'status' => 500 ) );
		}

		$detected_mime_type = wp_get_image_mime( $upload['file'] );
		if ( $mime_type !== $detected_mime_type ) {
			wp_delete_file( $upload['file'] );
			return new WP_Error( 'cfis_generation_mime_mismatch', __( 'The generated file content did not match the image format reported by the configured AI provider.', 'catalogframe-image-studio' ), array( 'status' => 502 ) );
		}

		$attachment_id = wp_insert_attachment(
			array(
				'post_mime_type' => $mime_type,
				'post_title'     => $product_name . ' - ' . $sku,
				'post_content'   => '',
				'post_excerpt'   => wp_trim_words( wp_strip_all_tags( $prompt ), 30 ),
				'post_status'    => 'inherit',
			),
			$upload['file']
		);

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

		require_once ABSPATH . 'wp-admin/includes/image.php';
		$metadata = wp_generate_attachment_metadata( $attachment_id, $upload['file'] );
		wp_update_attachment_metadata( $attachment_id, $metadata );
		update_post_meta( $attachment_id, '_cfis_sku', $sku );
		update_post_meta( $attachment_id, '_cfis_prompt', wp_strip_all_tags( $prompt ) );

		return array(
			'id'       => (int) $attachment_id,
			'url'      => wp_get_attachment_url( $attachment_id ),
			'filename' => basename( $upload['file'] ),
		);
	}

	/**
	 * Sanitizes an enum-like option.
	 *
	 * @param mixed  $value   Input value.
	 * @param array  $allowed Allowed values.
	 * @param string $default Default value.
	 * @return string
	 */
	private function sanitize_choice( $value, array $allowed, $default ) {
		$value = sanitize_key( (string) $value );
		return in_array( $value, $allowed, true ) ? $value : $default;
	}

}
