<?php
/**
 * REST API for batched scans.
 *
 * @package CertaCart_EU_Product_Readiness
 */

namespace CertaCart\EU_Product_Readiness;

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

final class Rest_Controller {
	private static ?Rest_Controller $instance = null;
	private const NS = 'certacart/v1';

	public static function instance(): Rest_Controller {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	public function register(): void {
		add_action( 'rest_api_init', array( $this, 'routes' ) );
	}

	public function routes(): void {
		register_rest_route(
			self::NS,
			'/scans',
			array(
				array(
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'start' ),
					'permission_callback' => array( $this, 'permission' ),
					'args'                => array(
						'category_id' => array( 'type' => 'integer', 'minimum' => 0, 'default' => 0 ),
						'eu_scope'    => array( 'type' => 'string', 'enum' => array( '', 'yes', 'review' ), 'default' => '' ),
					),
				),
			)
		);
		register_rest_route(
			self::NS,
			'/scans/(?P<id>\d+)',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'status' ),
					'permission_callback' => array( $this, 'permission' ),
				),
			)
		);
		register_rest_route(
			self::NS,
			'/scans/(?P<id>\d+)/process',
			array(
				array(
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'process' ),
					'permission_callback' => array( $this, 'permission' ),
				),
			)
		);
		register_rest_route(
			self::NS,
			'/scans/(?P<id>\d+)/cancel',
			array(
				array(
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'cancel' ),
					'permission_callback' => array( $this, 'permission' ),
				),
			)
		);
		register_rest_route(
			self::NS,
			'/scans/(?P<id>\d+)/results',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'results' ),
					'permission_callback' => array( $this, 'permission' ),
					'args'                => array(
						'page'     => array( 'type' => 'integer', 'minimum' => 1, 'default' => 1 ),
						'per_page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, 'default' => 50 ),
						'severity' => array( 'type' => 'string', 'enum' => array( '', 'critical', 'warning', 'ready', 'out_of_scope' ), 'default' => '' ),
						'search'   => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field' ),
						'rule'     => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_key' ),
						'task'     => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_key' ),
					),
				),
			)
		);
	}

	public function permission(): bool {
		return current_user_can( 'manage_woocommerce' );
	}

	public function start( \WP_REST_Request $request ) {
		try {
			$scope = array();
			if ( $request->get_param( 'category_id' ) ) {
				$scope['category_id'] = absint( $request->get_param( 'category_id' ) );
			}
			$eu_scope = Settings::sanitize_scan_scope( $request->get_param( 'eu_scope' ) );
			if ( '' !== $eu_scope ) {
				$scope['default_targets_eu'] = $eu_scope;
			}
			return rest_ensure_response( $this->decorate_status( ( new Scanner() )->start( get_current_user_id(), $scope ) ) );
		} catch ( \Throwable $throwable ) {
			return $this->error( $throwable );
		}
	}

	public function status( \WP_REST_Request $request ) {
		try {
			return rest_ensure_response( $this->decorate_status( ( new Scanner() )->status( absint( $request['id'] ) ) ) );
		} catch ( \Throwable $throwable ) {
			return $this->error( $throwable );
		}
	}

	public function process( \WP_REST_Request $request ) {
		try {
			return rest_ensure_response( $this->decorate_status( ( new Scanner() )->process( absint( $request['id'] ) ) ) );
		} catch ( \Throwable $throwable ) {
			return $this->error( $throwable );
		}
	}

	public function cancel( \WP_REST_Request $request ) {
		try {
			return rest_ensure_response( $this->decorate_status( ( new Scanner() )->cancel( absint( $request['id'] ) ) ) );
		} catch ( \Throwable $throwable ) {
			return $this->error( $throwable );
		}
	}

	public function results( \WP_REST_Request $request ): \WP_REST_Response {
		$scan_id    = absint( $request['id'] );
		$repository = new Scan_Repository();
		$scan       = $repository->get( $scan_id );
		if ( ! $scan ) {
			return new \WP_REST_Response( array( 'message' => __( 'Scan not found.', 'certacart-eu-product-readiness' ) ), 404 );
		}
		$args = array(
			'page'     => absint( $request->get_param( 'page' ) ?: 1 ),
			'per_page' => absint( $request->get_param( 'per_page' ) ?: 50 ),
			'severity' => (string) $request->get_param( 'severity' ),
			'search'   => sanitize_text_field( (string) $request->get_param( 'search' ) ),
			'rule'     => sanitize_key( (string) $request->get_param( 'rule' ) ),
			'task'     => sanitize_key( (string) $request->get_param( 'task' ) ),
		);
		$score_available = Scanner::is_score_available( $scan );
		$args['rules'] = $args['task'] && 'all' !== $args['task'] ? ( new Task_Aggregator() )->rules_for_task( $args['task'] ) : array();
		$rows = $repository->results( $scan_id, $args );
		if ( $rows ) {
			get_posts(
				array(
					'post_type'              => 'product',
					'post_status'            => 'any',
					'post__in'               => array_map( 'absint', array_column( $rows, 'product_id' ) ),
					'posts_per_page'         => count( $rows ),
					'orderby'                => 'post__in',
					'no_found_rows'          => true,
					'update_post_meta_cache' => false,
					'update_post_term_cache' => false,
				)
			);
		}
		$data = array();
		$field_tasks = array( 'manufacturer_details', 'manufacturer_location_unknown', 'eu_responsible_person', 'product_identifier_missing', 'safety_information_missing', 'storefront_display_disabled' );
		foreach ( $rows as $row ) {
			$findings = json_decode( (string) $row['findings_json'], true );
			$edit_url = get_edit_post_link( (int) $row['product_id'], 'raw' );
			if ( in_array( $args['task'], $field_tasks, true ) ) {
				$edit_url = add_query_arg( 'ccpr_task', $args['task'], $edit_url ) . '#ccpr_product_data';
			} elseif ( 'product_image_missing' === $args['task'] ) {
				$edit_url .= '#postimagediv';
			}
			$data[] = array(
				'product_id'     => (int) $row['product_id'],
				'product_name'   => $row['post_title'],
				'score'          => (float) $row['score'],
				'score_available' => $score_available,
				'in_scope'       => ! empty( $row['in_scope'] ),
				'critical_count' => (int) $row['critical_count'],
				'warning_count'  => (int) $row['warning_count'],
				'info_count'     => (int) $row['info_count'],
				'findings'       => is_array( $findings ) ? $findings : array(),
				'edit_url'       => $edit_url,
				'view_url'       => get_permalink( (int) $row['product_id'] ),
			);
		}
		$total = $repository->count_results( $scan_id, $args['severity'], $args['search'], $args['rule'], $args['rules'] );
		$response = new \WP_REST_Response( $data, 200 );
		$response->header( 'X-WP-Total', (string) $total );
		$response->header( 'X-WP-TotalPages', (string) max( 1, (int) ceil( $total / max( 1, $args['per_page'] ) ) ) );
		return $response;
	}

	private function error( \Throwable $throwable ): \WP_Error {
		$status = $throwable instanceof \InvalidArgumentException ? 404 : 500;
		return new \WP_Error( 'ccpr_scan_error', $throwable->getMessage(), array( 'status' => $status ) );
	}

	private function decorate_status( array $status ): array {
		$status['tasks']             = array();
		$status['affected_products'] = 0;
		$status['task_count']         = 0;
		$status['high_confidence_task_count'] = 0;
		if ( 'completed' !== ( $status['status'] ?? '' ) || empty( $status['id'] ) ) {
			return $status;
		}
		$scan_id                     = absint( $status['id'] );
		$all_tasks                   = ( new Task_Aggregator() )->for_scan( $scan_id );
		$status['tasks']             = array_slice( $all_tasks, 0, 5 );
		$status['task_count']        = count( $all_tasks );
		$status['high_confidence_task_count'] = count(
			array_filter(
				$all_tasks,
				static fn( array $task ): bool => 'high' === ( $task['confidence'] ?? '' )
			)
		);
		$status['affected_products'] = ( new Scan_Repository() )->affected_product_count( $scan_id );
		return $status;
	}
}
