<?php
/**
 * Proxy REST WordPress pour les scans Heatmap.
 *
 * @package Avision
 */

namespace Avision;

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

/**
 * Expose /wp-json/avision/v1/heatmap-scan et relaie vers FastAPI.
 *
 * La cle interne reste cote serveur WordPress. Le navigateur n'appelle jamais
 * directement /api/v1/heatmap/scan avec AVISION_INTERNAL_API_KEY.
 */
class Avision_Radar_Heatmap_REST {

	/**
	 * Coût crédits LocalEdge par taille de grille (embed).
	 *
	 * @var array<int,int>
	 */
	private static $embed_scan_costs = array(
		3  => 2,
		5  => 4,
		7  => 8,
		9  => 13,
		11 => 18,
		13 => 25,
		15 => 33,
		21 => 60,
	);

	/**
	 * Enregistre les hooks WordPress.
	 *
	 * @return void
	 */
	public static function init(): void {
		add_action( 'rest_api_init', array( __CLASS__, 'register_routes' ) );
	}

	/**
	 * Enregistre la route REST Heatmap.
	 *
	 * @return void
	 */
	public static function register_routes(): void {
		register_rest_route(
			'avision/v1',
			'/heatmap-scan',
			array(
				'methods'             => 'POST',
				'callback'            => array( __CLASS__, 'handle_scan' ),
				'permission_callback' => array( __CLASS__, 'permission_callback' ),
				'args'                => array(
					'profile_id' => array(
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
				),
			)
		);

		register_rest_route(
			'avision/v1',
			'/heatmap-latest',
			array(
				'methods'             => 'GET',
				'callback'            => array( __CLASS__, 'handle_latest' ),
				'permission_callback' => array( __CLASS__, 'permission_callback' ),
				'args'                => array(
					'profile_id' => array(
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
					'keyword'    => array(
						'required'          => true,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
			)
		);

		register_rest_route(
			'avision/v1',
			'/heatmap-history',
			array(
				'methods'             => 'GET',
				'callback'            => array( __CLASS__, 'handle_history' ),
				'permission_callback' => array( __CLASS__, 'permission_callback' ),
				'args'                => array(
					'profile_id' => array(
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
					'keyword'    => array(
						'required'          => true,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'limit'      => array(
						'required'          => false,
						'type'              => 'integer',
						'default'           => 10,
						'sanitize_callback' => 'absint',
					),
					'session_id' => array(
						'required'          => false,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
			)
		);

		register_rest_route(
			'avision/v1',
			'/heatmap-scan-by-id/(?P<scan_id>\d+)',
			array(
				'methods'             => 'GET',
				'callback'            => array( __CLASS__, 'handle_scan_by_id' ),
				'permission_callback' => array( __CLASS__, 'permission_callback' ),
				'args'                => array(
					'scan_id'    => array(
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
					'profile_id' => array(
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
				),
			)
		);
	}

	/**
	 * Verifie nonce REST + ACL profil existante.
	 *
	 * @param \WP_REST_Request $request Requete REST.
	 * @return bool|\WP_Error
	 */
	public static function permission_callback( \WP_REST_Request $request ) {
		// #Sprint44-Plugin — Auth embed token (mode iframe public)
		$embed_token = $request->get_header( 'X-Embed-Token' );
		if ( $embed_token === '' || $embed_token === null ) {
			$embed_token = $request->get_param( 'embed_token' );
		}
		$profile_id = absint( $request->get_param( 'profile_id' ) );
		if ( $embed_token && $profile_id ) {
			if ( class_exists( '\\Avision_Embed' ) &&
				\Avision_Embed::verify_embed_token( (string) $embed_token, $profile_id )
			) {
				return true;
			}
		}

		if ( ! function_exists( 'is_user_logged_in' ) || ! is_user_logged_in() ) {
			return new \WP_Error(
				'avision_heatmap_auth_required',
				'Authentification requise.',
				array( 'status' => 401 )
			);
		}

		$nonce = (string) $request->get_header( 'X-WP-Nonce' );
		if ( $nonce === '' ) {
			$nonce = (string) $request->get_param( '_wpnonce' );
		}
		if ( ! function_exists( 'wp_verify_nonce' ) || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
			return new \WP_Error(
				'avision_heatmap_bad_nonce',
				'Nonce REST invalide.',
				array( 'status' => 403 )
			);
		}

		if ( function_exists( 'avi_rest_check_profile_access' ) ) {
			return avi_rest_check_profile_access( $request );
		}

		return current_user_can( 'manage_options' );
	}

	/**
	 * Débite les crédits LocalEdge (avi_embed_credits) pour un scan embed.
	 *
	 * @param string $key_id     Clé embed.
	 * @param int    $profile_id ID profil.
	 * @param int    $grid_size  Taille grille.
	 * @return bool|string true si OK, code erreur sinon.
	 */
	private static function debit_embed_credits(
		string $key_id,
		int $profile_id,
		int $grid_size
	) {
		global $wpdb;

		$cost = self::$embed_scan_costs[ $grid_size ]
			?? $grid_size * $grid_size;

		$row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT id,
				        GREATEST(0,
				            credits_total
				            + COALESCE(credits_bonus,0)
				            - credits_used
				        ) AS available
				 FROM `{$wpdb->prefix}avi_embed_credits`
				 WHERE key_id = %s
				   AND profile_id = %d
				 LIMIT 1",
				$key_id,
				$profile_id
			)
		);

		if ( ! $row ) {
			return 'no_credit_account';
		}

		if ( (int) $row->available < $cost ) {
			return 'insufficient_credits';
		}

		$updated = $wpdb->query(
			$wpdb->prepare(
				"UPDATE `{$wpdb->prefix}avi_embed_credits`
				 SET credits_used = credits_used + %d
				 WHERE id = %d",
				$cost,
				(int) $row->id
			)
		);

		return $updated !== false ? true : 'db_error';
	}

	/**
	 * Valide la demande puis relaie le scan vers FastAPI.
	 *
	 * @param \WP_REST_Request $request Requete REST.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function handle_scan( \WP_REST_Request $request ) {
		$profile_id = absint( $request->get_param( 'profile_id' ) );
		if ( $profile_id <= 0 ) {
			return new \WP_Error(
				'avision_heatmap_invalid_profile',
				'profile_id invalide.',
				array( 'status' => 400 )
			);
		}

		$params = $request->get_json_params();
		if ( ! is_array( $params ) ) {
			$params = array();
		}

		$keyword = isset( $params['keyword'] ) ? sanitize_text_field( (string) $params['keyword'] ) : '';
		$keyword = trim( $keyword );
		if ( $keyword === '' ) {
			return new \WP_Error(
				'avision_heatmap_invalid_keyword',
				'keyword est requis.',
				array( 'status' => 400 )
			);
		}

		$center = isset( $params['center'] ) && is_array( $params['center'] ) ? $params['center'] : array();
		$lat    = isset( $center['lat'] ) && is_numeric( $center['lat'] ) ? (float) $center['lat'] : null;
		$lng    = isset( $center['lng'] ) && is_numeric( $center['lng'] ) ? (float) $center['lng'] : null;
		if ( $lat === null || $lng === null || $lat < -90 || $lat > 90 || $lng < -180 || $lng > 180 ) {
			return new \WP_Error(
				'avision_heatmap_invalid_center',
				'center.lat et center.lng sont requis.',
				array( 'status' => 400 )
			);
		}

		$allowed_grid_sizes = array( 3, 5, 7, 9, 11, 13, 15, 17, 19, 21 );
		$grid_size          = isset( $params['grid_size'] ) ? absint( $params['grid_size'] ) : 7;
		if ( ! in_array( $grid_size, $allowed_grid_sizes, true ) ) {
			return new \WP_Error(
				'avision_heatmap_invalid_grid_size',
				'grid_size doit etre parmi 3,5,7,9,11,13,15,17,19,21.',
				array( 'status' => 422 )
			);
		}

		$radius_km = isset( $params['radius_km'] ) && is_numeric( $params['radius_km'] ) ? (float) $params['radius_km'] : 5.0;
		if ( $radius_km <= 0 ) {
			return new \WP_Error(
				'avision_heatmap_invalid_radius',
				'radius_km doit etre strictement positif.',
				array( 'status' => 422 )
			);
		}

		$scan_mode = isset( $params['scan_mode'] ) ? strtolower( sanitize_text_field( (string) $params['scan_mode'] ) ) : 'standard';
		if ( ! in_array( $scan_mode, array( 'standard', 'live' ), true ) ) {
			$scan_mode = 'standard';
		}
		$scan_session_id = isset( $params['scan_session_id'] )
			? sanitize_text_field( (string) $params['scan_session_id'] )
			: '';

		$internal_key = self::get_internal_api_key();
		if ( $internal_key === '' ) {
			return new \WP_Error(
				'avision_heatmap_internal_key_missing',
				'Configuration serveur Heatmap incomplete.',
				array( 'status' => 500 )
			);
		}

		$embed_token = $request->get_header( 'X-Embed-Token' );
		if ( ! $embed_token ) {
			$embed_token = $request->get_param( 'embed_token' );
		}
		$is_embed_mode = (
			$embed_token
			&& class_exists( '\Avision_Embed' )
			&& \Avision_Embed::verify_embed_token(
				(string) $embed_token,
				$profile_id
			)
		);

		if ( $is_embed_mode ) {
			global $wpdb;

			$key_id = sanitize_text_field(
				(string) ( $request->get_param( 'key_id' )
					?: ( $params['key_id'] ?? '' ) )
			);
			if ( $key_id === '' ) {
				$key_row = $wpdb->get_row(
					$wpdb->prepare(
						"SELECT key_id FROM
						 `{$wpdb->prefix}avi_embed_keys`
						 WHERE profile_id = %d
						   AND status = 'active'
						 LIMIT 1",
						$profile_id
					)
				);
				$key_id = $key_row ? (string) $key_row->key_id : '';
			}

			$debit_result = self::debit_embed_credits(
				$key_id,
				$profile_id,
				$grid_size
			);

			if ( $debit_result === 'no_credit_account' ) {
				return new \WP_Error(
					'embed_no_credit_account',
					'Aucun compte crédits LocalEdge trouvé',
					array( 'status' => 402 )
				);
			}
			if ( $debit_result === 'insufficient_credits' ) {
				return new \WP_Error(
					'embed_insufficient_credits',
					'Crédits LocalEdge insuffisants',
					array( 'status' => 402 )
				);
			}
			if ( $debit_result === 'db_error' ) {
				return new \WP_Error(
					'embed_credit_debit_error',
					'Erreur débit crédits',
					array( 'status' => 500 )
				);
			}
		}

		$payload = array(
			'keyword'         => $keyword,
			'center'          => array(
				'lat' => $lat,
				'lng' => $lng,
			),
			'grid_size'       => $grid_size,
			'radius_km'       => $radius_km,
			'scan_mode'       => $scan_mode,
			'scan_session_id' => $scan_session_id,
		);

		if ( $is_embed_mode ) {
			$payload['skip_credit_check'] = true;
		}

		$webhook_url = isset( $params['webhook_url'] ) ? esc_url_raw( (string) $params['webhook_url'] ) : '';
		if ( $webhook_url !== '' ) {
			$payload['webhook_url'] = $webhook_url;
		}

		$response = wp_remote_post(
			self::get_backend_url() . '/heatmap/scan',
			array(
				'timeout' => 30,
				'headers' => array(
					'Content-Type'  => 'application/json',
					'Authorization' => 'Bearer ' . $internal_key,
					'X-WP-User-ID'  => (string) self::resolve_user_id_for_profile( $profile_id ),
					'X-Business-ID' => (string) $profile_id,
				),
				'body'    => wp_json_encode( $payload ),
			)
		);

		return self::proxy_response( $response );
	}

	/**
	 * Relaye la demande du dernier scan Heatmap pour un mot-cle vers FastAPI.
	 *
	 * @param \WP_REST_Request $request Requete REST.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function handle_latest( \WP_REST_Request $request ) {
		$profile_id = absint( $request->get_param( 'profile_id' ) );
		if ( $profile_id <= 0 ) {
			return new \WP_Error(
				'avision_heatmap_invalid_profile',
				'profile_id invalide.',
				array( 'status' => 400 )
			);
		}

		$keyword = trim( sanitize_text_field( (string) $request->get_param( 'keyword' ) ) );
		if ( $keyword === '' ) {
			return new \WP_Error(
				'avision_heatmap_invalid_keyword',
				'keyword est requis.',
				array( 'status' => 400 )
			);
		}

		$internal_key = self::get_internal_api_key();
		if ( $internal_key === '' ) {
			return new \WP_Error(
				'avision_heatmap_internal_key_missing',
				'Configuration serveur Heatmap incomplete.',
				array( 'status' => 500 )
			);
		}

		$url = add_query_arg(
			array( 'keyword' => $keyword ),
			self::get_backend_url() . '/heatmap/latest'
		);

		$response = wp_remote_get(
			$url,
			array(
				'timeout' => 30,
				'headers' => array(
					'Content-Type'  => 'application/json',
					'Authorization' => 'Bearer ' . $internal_key,
					'X-WP-User-ID'  => (string) self::resolve_user_id_for_profile( $profile_id ),
					'X-Business-ID' => (string) $profile_id,
				),
			)
		);

		return self::proxy_response( $response );
	}

	/**
	 * Relaye l'historique des scans Heatmap pour un mot-cle vers FastAPI.
	 * Route WP: /wp-json/avision/v1/heatmap-history, callback handle_history.
	 *
	 * @param \WP_REST_Request $request Requete REST.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function handle_history( \WP_REST_Request $request ) {
		$profile_id = absint( $request->get_param( 'profile_id' ) );
		if ( $profile_id <= 0 ) {
			return new \WP_Error(
				'avision_heatmap_invalid_profile',
				'profile_id invalide.',
				array( 'status' => 400 )
			);
		}

		$keyword = trim( sanitize_text_field( (string) $request->get_param( 'keyword' ) ) );
		if ( $keyword === '' ) {
			return new \WP_Error(
				'avision_heatmap_invalid_keyword',
				'keyword est requis.',
				array( 'status' => 400 )
			);
		}

		$limit = absint( $request->get_param( 'limit' ) );
		$limit = max( 1, min( 50, $limit > 0 ? $limit : 10 ) );
		$session_id = trim( sanitize_text_field( (string) $request->get_param( 'session_id' ) ) );

		$internal_key = self::get_internal_api_key();
		if ( $internal_key === '' ) {
			return new \WP_Error(
				'avision_heatmap_internal_key_missing',
				'Configuration serveur Heatmap incomplete.',
				array( 'status' => 500 )
			);
		}

		$query_args = array(
			'keyword' => $keyword,
			'limit'   => $limit,
		);
		if ( $session_id !== '' ) {
			$query_args['session_id'] = $session_id;
		}
		$url = add_query_arg( $query_args, self::get_backend_url() . '/heatmap/history' );

		$response = wp_remote_get(
			$url,
			array(
				'timeout' => 30,
				'headers' => array(
					'Content-Type'  => 'application/json',
					'Authorization' => 'Bearer ' . $internal_key,
					'X-WP-User-ID'  => (string) self::resolve_user_id_for_profile( $profile_id ),
					'X-Business-ID' => (string) $profile_id,
				),
			)
		);

		return self::proxy_response( $response );
	}

	/**
	 * Résolution user_id pour mode embed (propriétaire du profil si session WP absente).
	 * #Sprint44-Plugin
	 *
	 * @param int $profile_id ID avi_saas_profiles.
	 * @return int
	 */
	private static function resolve_user_id_for_profile( int $profile_id ): int {
		$uid = get_current_user_id();
		if ( $uid > 0 ) {
			return $uid;
		}
		global $wpdb;
		$owner_id = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT user_id FROM `{$wpdb->prefix}avi_saas_profiles`
				 WHERE id = %d LIMIT 1",
				$profile_id
			)
		);
		return $owner_id ? (int) $owner_id : 0;
	}

	/**
	 * Relaye la recuperation d un scan par son ID vers FastAPI.
	 * Route WP: /wp-json/avision/v1/heatmap-scan-by-id/{scan_id}, callback handle_scan_by_id.
	 *
	 * @param \WP_REST_Request $request Requete REST.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function handle_scan_by_id( \WP_REST_Request $request ) {
		$scan_id = absint( $request->get_param( 'scan_id' ) );
		if ( $scan_id <= 0 ) {
			return new \WP_Error(
				'avision_heatmap_invalid_scan_id',
				'scan_id invalide.',
				array( 'status' => 400 )
			);
		}
		$profile_id = absint( $request->get_param( 'profile_id' ) );
		if ( $profile_id <= 0 ) {
			return new \WP_Error(
				'avision_heatmap_invalid_profile',
				'profile_id invalide.',
				array( 'status' => 400 )
			);
		}

		$internal_key = self::get_internal_api_key();
		if ( $internal_key === '' ) {
			return new \WP_Error(
				'avision_heatmap_internal_key_missing',
				'Configuration serveur Heatmap incomplete.',
				array( 'status' => 500 )
			);
		}

		$url = self::get_backend_url() . '/heatmap/scan/' . $scan_id;

		$response = wp_remote_get(
			$url,
			array(
				'timeout' => 30,
				'headers' => array(
					'Content-Type'  => 'application/json',
					'Authorization' => 'Bearer ' . $internal_key,
					'X-WP-User-ID'  => (string) self::resolve_user_id_for_profile( $profile_id ),
					'X-Business-ID' => (string) $profile_id,
				),
			)
		);

		return self::proxy_response( $response );
	}

	/**
	 * URL interne FastAPI.
	 *
	 * @return string
	 */
	private static function get_backend_url(): string {
		$base = '';
		if ( defined( 'AVISION_BACKEND_INTERNAL_URL' ) && is_string( AVISION_BACKEND_INTERNAL_URL ) ) {
			$base = trim( (string) AVISION_BACKEND_INTERNAL_URL );
		}
		if ( $base === '' && function_exists( 'getenv' ) ) {
			$env = getenv( 'AVISION_BACKEND_INTERNAL_URL' );
			$base = is_string( $env ) ? trim( $env ) : '';
		}
		if ( $base === '' ) {
			$base = 'http://127.0.0.1:8001';
		}

		return rtrim( $base, '/' );
	}

	/**
	 * Cle interne serveur vers FastAPI.
	 *
	 * @return string
	 */
	private static function get_internal_api_key(): string {
		$key = '';
		if ( defined( 'AVISION_INTERNAL_API_KEY' ) && is_string( AVISION_INTERNAL_API_KEY ) ) {
			$key = trim( (string) AVISION_INTERNAL_API_KEY );
		}
		if ( $key === '' && function_exists( 'getenv' ) ) {
			$env = getenv( 'AVISION_INTERNAL_API_KEY' );
			$key = is_string( $env ) ? trim( $env ) : '';
		}

		return $key;
	}

	/**
	 * Retourne au front la reponse FastAPI sans masquer les statuts metier.
	 *
	 * @param array|\WP_Error $response Reponse wp_remote_post.
	 * @return \WP_REST_Response|\WP_Error
	 */
	private static function proxy_response( $response ) {
		if ( is_wp_error( $response ) ) {
			return new \WP_Error(
				'avision_heatmap_backend_unreachable',
				$response->get_error_message(),
				array( 'status' => 502 )
			);
		}

		$status = (int) wp_remote_retrieve_response_code( $response );
		$body   = (string) wp_remote_retrieve_body( $response );
		$data   = null;
		if ( $body !== '' ) {
			$decoded = json_decode( $body, true );
			$data    = is_array( $decoded ) ? $decoded : array( 'raw' => $body );
		}
		if ( ! is_array( $data ) ) {
			$data = array();
		}

		if ( $status <= 0 ) {
			$status = 502;
		}

		return new \WP_REST_Response( $data, $status );
	}
}

Avision_Radar_Heatmap_REST::init();
