<?php
/**
 * Abstract Base Controller for all Bulk Generator types
 *
 * Provides shared infrastructure for permission checks, rate limiting,
 * response formatting, and AiSupportGate enforcement across all bulk
 * generator controllers (Description, Title, FAQ, SeoMeta, Standard).
 *
 * Subclasses MUST:
 *   - Implement `register_routes()`.
 *   - Override `RATE_LIMIT_SCOPE` with their generator-specific scope string.
 *
 * Security model:
 *   - `check_permissions()` — capability check only (for read/GET endpoints).
 *   - `check_permissions_write()` — capability + REST nonce check (for write endpoints).
 *
 * @package AIPT\Api\BulkGenerator
 * @since   2.6.0
 */

namespace AIPT\Api\BulkGenerator;

use AIPT\Api\Traits\RateLimitTrait;
use WP_Error;
use WP_REST_Response;

/**
 * Abstract base controller with shared bulk-generator infrastructure.
 */
abstract class BaseBulkGeneratorController {
	use RateLimitTrait;

	/**
	 * REST API namespace shared by all AIPT v1 endpoints.
	 */
	protected const REST_NAMESPACE = 'aipt/v1';

	/**
	 * Rate-limit scope identifier.
	 *
	 * Subclasses MUST override this constant with a unique, lowercase string
	 * that identifies their generator type (e.g. 'title', 'faq', 'seo_meta').
	 * This value is passed to `RateLimitTrait::checkRateLimit()` as the scope
	 * parameter and forms part of the transient key
	 * (`aipt_rate_limit_{scope}_{user_id}`).
	 *
	 * Keeping each generator on a separate scope means a flood of title
	 * generation requests will not consume the FAQ or SeoMeta allowances.
	 */
	protected const RATE_LIMIT_SCOPE = 'default';

	/**
	 * Register REST routes — must be implemented by each subclass.
	 *
	 * @return void
	 */
	abstract public function register_routes();

	/**
	 * Check read permission for GET endpoints.
	 *
	 * Called as `permission_callback` on read-only REST routes. Returns a
	 * simple boolean because GET endpoints do not require nonce validation
	 * (nonces are CSRF tokens; GET requests are safe and idempotent).
	 *
	 * @return bool True when the current user has `manage_woocommerce`.
	 */
	public function check_permissions(): bool {
		return current_user_can( 'manage_woocommerce' );
	}

	/**
	 * Check write permission for POST/PUT/DELETE endpoints.
	 *
	 * Validates both authorization and the `wp_rest` nonce for mutating admin
	 * requests. Frontend callers should send the nonce in the `X-WP-Nonce`
	 * header; `_wpnonce` is accepted as a fallback for form-style requests.
	 *
	 * @param mixed $_request The REST request object (unused; accepted for callback compatibility).
	 * @return bool|WP_Error True if authorized, WP_Error with 403 otherwise.
	 */
	public function check_permissions_write( $_request = null ) {
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return new WP_Error(
				'rest_forbidden',
				__( 'You do not have permission to perform this action.', 'ai-product-tools' ),
				array( 'status' => 403 )
			);
		}

		$nonce = '';
		if ( is_object( $_request ) && method_exists( $_request, 'get_header' ) ) {
			$nonce = (string) $_request->get_header( 'X-WP-Nonce' );
		}

		if ( '' === $nonce && is_object( $_request ) && method_exists( $_request, 'get_param' ) ) {
			$nonce = (string) $_request->get_param( '_wpnonce' );
		}

		if ( '' === $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
			return new WP_Error(
				'rest_cookie_invalid_nonce',
				__( 'Cookie check failed', 'ai-product-tools' ),
				array( 'status' => 403 )
			);
		}

		return true;
	}

	/**
	 * Check whether AI is supported on this WordPress installation.
	 *
	 * All AI-firing endpoints MUST call this at the top of their callback.
	 * Returns `null` when AI is available, or a `WP_Error` (503) when the site
	 * administrator has disabled AI via `WP_AI_SUPPORT` constant or the
	 * `wp_supports_ai` filter.
	 *
	 * @return WP_Error|null Null when AI is available, WP_Error otherwise.
	 */
	protected function check_ai_support_gate(): ?WP_Error {
		$result = \AIPT\Core\Services\AiSupportGate::guard();
		return ( $result instanceof WP_Error ) ? $result : null;
	}

	/**
	 * Convenience wrapper around RateLimitTrait::checkRateLimit().
	 *
	 * Resolves the scope from the calling subclass's `RATE_LIMIT_SCOPE`
	 * constant via late static binding, so each generator type gets its own
	 * isolated rate-limit window. Callers may pass an explicit `$scope` to
	 * override the constant (useful in tests).
	 *
	 * @param int         $user_id Authenticated user ID.
	 * @param string|null $scope   Optional scope override; defaults to static::RATE_LIMIT_SCOPE.
	 * @return array{allowed: bool, remaining: int, reset_in: int}
	 */
	protected function check_rate_limit( int $user_id, ?string $scope = null ): array {
		return $this->checkRateLimit( $user_id, $scope ?? static::RATE_LIMIT_SCOPE );
	}

	/**
	 * Build a uniform success response envelope.
	 *
	 * @param array  $data    Response payload.
	 * @param string $message Optional human-readable success message.
	 * @return WP_REST_Response HTTP 200 with `{ success: true, data: ..., [message: ...] }`.
	 */
	protected function success_response( array $data, string $message = '' ): WP_REST_Response {
		$response = array(
			'success' => true,
			'data'    => $data,
		);

		if ( ! empty( $message ) ) {
			$response['message'] = $message;
		}

		return new WP_REST_Response( $response, 200 );
	}

	/**
	 * Build a uniform error WP_Error.
	 *
	 * @param string $code    Machine-readable error code.
	 * @param string $message Human-readable error message.
	 * @param int    $status  HTTP status code (default 400).
	 * @return WP_Error
	 */
	protected function error_response( string $code, string $message, int $status = 400 ): WP_Error {
		return new WP_Error( $code, $message, array( 'status' => $status ) );
	}

	/**
	 * Format an AI provider API error message with the provider name prefix.
	 *
	 * Delegates to `AIPT\Core\ErrorHandler` for consistent error presentation
	 * across all controllers.
	 *
	 * @param string $provider      Provider name (e.g. 'OpenAI', 'Gemini').
	 * @param string $error_message Raw error message from the provider client.
	 * @return string Formatted error string ready for display.
	 */
	protected function format_api_error_message( string $provider, string $error_message ): string {
		return \AIPT\Core\ErrorHandler::format_api_error_message( $provider, $error_message );
	}
}
