<?php

declare(strict_types=1);
if (!defined('WPINC')) {
    die();
}

/**
 * Class CPEPAI_Backend_Service
 * Handles interaction with the backend API and authentication.
 */
class CPEPAI_Backend_Service
{
    /**
     * The backend API base URL.
     *
     * @var string
     */
    private string $api_base_url;

    /**
     * Rate limit window in milliseconds.
     *
     * @var int
     */
    private int $rate_limit_window_ms;

    /**
     * Maximum requests per rate limit window.
     *
     * @var int
     */
    private int $rate_limit_max_requests;

    /**
     * Nonce expiry time in milliseconds.
     *
     * @var int
     */
    private int $nonce_expiry_ms;

    /**
     * The logging service instance.
     *
     * @var CPEPAI_Logging_Service|null
     */
    private ?CPEPAI_Logging_Service $logging_service = null;

    /**
     * Mutex/lock to prevent simultaneous backend API calls
     * This prevents rate limit issues when multiple endpoints call backend simultaneously
     *
     * @var bool
     */
    private static bool $backend_api_lock = false;

    /**
     * Queue of pending backend API calls
     *
     * @var array
     */
    private static array $backend_api_queue = [];

    /**
     * Constructor.
     *
     * @param CPEPAI_Database_Service|null $database_service Optional database service for logging.
     */
    public function __construct(?CPEPAI_Database_Service $database_service = null)
    {
        $this->initialize_config();

        // Initialize logging service if database service is provided
        if ($database_service !== null) {
            $this->logging_service = new CPEPAI_Logging_Service($database_service);
        }
    }

    /**
     * Get the secret key from Freemius SDK.
     *
     * @return string|false The secret key or false if not available.
     */
    protected function get_site_secret_key()
    {
        if (!function_exists('cpepai_fs')) {
            return false;
        }
        $fs = cpepai_fs();
        if (!is_object($fs)) {
            return false;
        }
        if (!method_exists($fs, 'get_storage')) {
            return false;
        }
        $site = $fs->get_site();
        if (empty($site)) {
            return false;
        }
        $site_secret_key = null;
        if (is_array($site)) {
            $site_secret_key = $site['secret_key'] ?? ($site['install_secret_key'] ?? null);
        }
        if (is_object($site)) {
            $site_secret_key = $site->secret_key ?? ($site->install_secret_key ?? null);
        }
        return $site_secret_key ?: false;
    }

    /**
     * Get the install ID from Freemius SDK.
     *
     * @return string|false The install ID or false if not available.
     */
    protected function get_site_install_id()
    {
        if (!function_exists('cpepai_fs')) {
            return false;
        }
        $fs = cpepai_fs();
        if (!is_object($fs)) {
            return false;
        }
        if (method_exists($fs, 'get_install_id')) {
            return $fs->get_install_id();
        }
        $site = $fs->get_site();
        if (empty($site)) {
            return false;
        }
        $install_id = null;
        if (is_array($site)) {
            $install_id = $site['id'] ?? ($site['install_id'] ?? null);
        }
        if (is_object($site)) {
            $install_id = $site->id ?? ($site->install_id ?? null);
        }
        return $install_id ?: false;
    }

    /**
     * Initialize configuration with hardcoded constants.
     *
     * @return void
     */
    private function initialize_config(): void
    {
        $this->api_base_url = 'https://api.contentplan.app/';

        $this->rate_limit_window_ms = 900000; // 15 minutes
        $this->rate_limit_max_requests = 100;
        $this->nonce_expiry_ms = 300000; // 5 minutes
    }

    /**
     * Generate HMAC signature for request authentication.
     *
     * @param string $payload The request payload.
     * @param string $secret_key The secret key for HMAC generation.
     * @return string The HMAC signature.
     */
    protected function generate_hmac_signature(string $payload, string $secret_key): string
    {
        // Calculate HMAC
        $signature = hash_hmac('sha256', $payload, $secret_key);

        return $signature;
    }

    /**
     * Generate a cryptographically secure nonce.
     *
     * @return string The generated nonce.
     */
    protected function generate_nonce(): string
    {
        return bin2hex(random_bytes(32));
    }

    /**
     * Get current timestamp in milliseconds.
     *
     * @return string The current timestamp as integer string.
     */
    protected function get_current_timestamp(): string
    {
        // Ensure timestamp is an integer (not float) to match JavaScript Date.now()
        return (string) intval(microtime(true) * 1000);
    }

    /**
     * Prepare authentication headers for backend requests.
     *
     * @param string $method The HTTP method.
     * @param string $path The request path.
     * @param array $body The request body.
     * @param string $secret_key The secret key for HMAC.
     * @return array The authentication headers.
     */
    protected function prepare_auth_headers(
        string $method,
        string $path,
        $body,
        string $secret_key,
        string $install_id = ''
    ): array {
        $timestamp = $this->get_current_timestamp();
        $nonce = $this->generate_nonce();

        // Match backend payload structure exactly:
        // Backend uses: { body: req.body, installId, method, nonce, path, timestamp }
        // Backend comment says "Sort keys" but doesn't actually sort - uses insertion order
        // JavaScript objects maintain insertion order, so we must match the exact order

        // Extract installId from body if not provided separately (handle both array and object)
        $body_install_id = null;
        if (is_array($body)) {
            $body_install_id = $body['installId'] ?? null;
        } elseif (is_object($body)) {
            $body_install_id = $body->installId ?? null;
        }

        $payloadData = [
            'body' => $body,
            'installId' => !empty($install_id) ? $install_id : $body_install_id,
            'method' => $method,
            'nonce' => $nonce,
            'path' => $path,
            'timestamp' => $timestamp
        ];
        // Don't sort - match backend's insertion order exactly
        $payload = json_encode($payloadData, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        // Create the message in the format: timestamp:nonce:payload
        $message = $timestamp . ':' . $nonce . ':' . $payload;

        $signature = $this->generate_hmac_signature($message, $secret_key);

        return [
            'Authorization' => 'HMAC-SHA256 ' . $signature,
            'X-Timestamp' => $timestamp,
            'X-Nonce' => $nonce,
            'Content-Type' => 'application/json'
        ];
    }

    /**
     * Make an authenticated request to the backend API.
     *
     * @param string $endpoint The API endpoint.
     * @param string $method The HTTP method.
     * @param array $data The request data.
     * @param string $secret_key The secret key for authentication.
     * @param string $install_id The install ID for Freemius authentication.
     * @param string $hmac_path Optional custom path for HMAC calculation. If not provided, calculated from endpoint.
     * @return array The response data.
     */
    public function make_authenticated_request(
        string $endpoint,
        string $method = 'GET',
        array $data = [],
        string $secret_key = '',
        string $install_id = '',
        string $hmac_path = ''
    ): array {
        // Mutex: Wait for any ongoing backend API call to complete
        // This prevents rate limit issues when multiple endpoints call backend simultaneously
        $max_wait_time = 5; // Maximum seconds to wait
        $wait_interval = 0.1; // Check every 100ms
        $waited = 0;

        while (self::$backend_api_lock && $waited < $max_wait_time) {
            usleep((int) ($wait_interval * 1000000)); // Sleep in microseconds
            $waited += $wait_interval;
        }

        // Acquire lock
        self::$backend_api_lock = true;

        try {
            // Fetch credentials internally if not provided
            if (empty($secret_key)) {
                $secret_key = $this->get_site_secret_key();
                if ($secret_key === false) {
                    $result = [
                        'success' => false,
                        'status_code' => 401,
                        'error' => 'Secret key not available. Please ensure the plugin is properly activated.'
                    ];
                    return $result;
                }
            }

            if (empty($install_id)) {
                $install_id = $this->get_site_install_id();
                if ($install_id === false) {
                    $result = [
                        'success' => false,
                        'status_code' => 400,
                        'error' => 'Install ID not available. Please ensure the plugin is properly activated.'
                    ];
                    return $result;
                }
            }

            $url = rtrim($this->api_base_url, '/') . '/' . ltrim($endpoint, '/');

            // Add install ID to the request data if provided and not already present
            if (!empty($install_id) && !isset($data['installId'])) {
                $data['installId'] = $install_id;
            }

            // For GET requests, add install ID as query parameter
            if ($method === 'GET' && !empty($install_id)) {
                $url .= (strpos($url, '?') !== false ? '&' : '?') . 'installId=' . urlencode($install_id);
            }

            $headers = [];
            if (!empty($secret_key)) {
                // Use custom HMAC path if provided, otherwise calculate from endpoint
                if (!empty($hmac_path)) {
                    $path = $hmac_path;
                } else {
                    // Strip the router mount point (cpepai) for HMAC calculation
                    $path = '/' . ltrim($endpoint, '/');
                    $path = preg_replace('#^/cpepai/#', '/', $path);
                }

                // For HMAC calculation, match backend structure:
                // Backend uses req.body (empty {} for GET) and extracts installId separately
                // So for GET requests, use empty body but include installId at top level
                $hmac_body = $data;
                if ($method === 'GET') {
                    // For GET requests, body should be empty {} (object) in HMAC payload, not [] (array)
                    // PHP's json_encode([]) produces [] but JavaScript's JSON.stringify({}) produces {}
                    // We need to use an object, not an array, to match backend
                    $hmac_body = new \stdClass(); // Empty object, not array
                }

                $headers = $this->prepare_auth_headers($method, $path, $hmac_body, $secret_key, $install_id);
            }

            $args = [
                'method' => $method,
                'headers' => $headers,
                'timeout' => 30,
                'sslverify' => true
            ];

            if (!empty($data) && in_array($method, ['POST', 'PUT', 'PATCH'])) {
                $args['body'] = json_encode($data);
            }

            $response = wp_remote_request($url, $args);

            if (is_wp_error($response)) {
                $result = [
                    'success' => false,
                    'error' => $response->get_error_message()
                ];
                return $result;
            }

            $status_code = wp_remote_retrieve_response_code($response);
            $body = wp_remote_retrieve_body($response);

            $decoded_body = json_decode($body, true);

            $success = $status_code >= 200 && $status_code < 300;
            $error_message = null;
            if (!$success) {
                if (is_array($decoded_body)) {
                    $error_message = $decoded_body['message'] ?? ($decoded_body['error'] ?? null);
                }
                if (empty($error_message)) {
                    // Use first 200 chars of body as error message, or status code
                    $body_preview = !empty($body) ? substr($body, 0, 200) : '';
                    $error_message = $body_preview !== '' ? $body_preview : 'HTTP ' . $status_code;
                }
            }

            return [
                'success' => $success,
                'status_code' => $status_code,
                'data' => $decoded_body,
                'raw_body' => $body,
                'error' => $error_message
            ];
        } finally {
            // Release lock to allow next backend API call
            self::$backend_api_lock = false;
        }
    }

    /**
     * Get the backend API base URL.
     *
     * @return string The API base URL.
     */
    public function get_api_base_url(): string
    {
        return $this->api_base_url;
    }

    /**
     * Get product ID from Freemius SDK for debugging/dev info.
     *
     * @return string Product ID if available, empty string otherwise.
     */
    public function get_product_id(): string
    {
        if (!function_exists('cpepai_fs')) {
            return '';
        }
        $fs = cpepai_fs();
        if (!is_object($fs)) {
            return '';
        }
        if (method_exists($fs, 'get_plugin')) {
            $plugin = $fs->get_plugin();
            if (is_object($plugin)) {
                $id = $plugin->id ?? '';
                return is_scalar($id) ? (string) $id : '';
            }
        }
        return '';
    }

    /**
     * Check if the service is properly configured.
     *
     * @return bool True if properly configured, false otherwise.
     */
    public function is_configured(): bool
    {
        return !empty($this->api_base_url);
    }

    /**
     * Generate content plan using the backend API.
     *
     * @param array $parameters The content plan generation parameters.
     * @return array The response data.
     */
    public function generate_content_plan(array $parameters): array
    {
        $endpoint = 'cpepai/generate';

        // Forward parameters directly to backend (already shaped per backend schema)
        $request_data = $parameters;

        // Make authenticated request to backend API
        // Credentials will be fetched internally by make_authenticated_request
        $response = $this->make_authenticated_request($endpoint, 'POST', $request_data);

        // Log the request (always log, including failures)
        if ($this->logging_service !== null) {
            $task_id = null;
            if (isset($response['data']) && is_array($response['data'])) {
                // Try nested path first: $response['data']['data']['taskId']
                if (isset($response['data']['data']['taskId'])) {
                    $task_id = $response['data']['data']['taskId'];
                } elseif (isset($response['data']['taskId'])) {
                    $task_id = $response['data']['taskId'];
                }
            }

            // Calculate tokens spent only if request succeeded: 1 token per post in plan
            $tokens_spent = null;
            if ($response['success'] && isset($parameters['total_posts']) && is_numeric($parameters['total_posts'])) {
                $tokens_spent = (int) $parameters['total_posts'] * 1; // TOKENS_PER_POST_IN_PLAN = 1
            }

            $level = $response['success'] ? 'info' : 'error';

            // Build error message with moderation context if available
            $error_message = null;
            if (!$response['success']) {
                $error_message = $response['error'] ?? 'Unknown error';

                // Check if this is a moderation/blocked user error
                $status_code = $response['status_code'] ?? null;
                if ($status_code === 403) {
                    // Check if response data contains moderation information
                    if (isset($response['data']['message'])) {
                        $error_message = $response['data']['message'];
                    } elseif (isset($response['data']['error'])) {
                        $error_message = $response['data']['error'];
                    }

                    // Check for moderation-specific error codes or messages
                    if (isset($response['data']['reason'])) {
                        $error_message .= ' (Reason: ' . $response['data']['reason'] . ')';
                    }
                }
            }

            $message = $response['success']
                ? 'Content plan generation request completed successfully'
                : 'Content plan generation request failed: ' . ($error_message ?? 'Unknown error');

            $context = [
                'endpoint' => $endpoint,
                'status_code' => $response['status_code'] ?? null,
                'success' => $response['success']
            ];

            if ($tokens_spent !== null) {
                $context['tokens_spent'] = $tokens_spent;
            }

            if (!$response['success']) {
                if ($error_message) {
                    $context['error'] = $error_message;
                }
                // Include full response data for debugging moderation failures
                if (isset($response['data']) && is_array($response['data'])) {
                    $context['response_data'] = $response['data'];
                }
            }

            $this->logging_service->write_to_log_table(
                $level,
                'content_plan_generation',
                $message,
                $context,
                $task_id,
                null,
                null,
                $tokens_spent
            );
        }

        return $response;
    }

    /**
     * Generate post content using the backend API.
     *
     * @param array $parameters The post generation parameters (title, keywords, structure, language).
     * @return array The response data.
     */
    public function generate_post(array $parameters): array
    {
        $endpoint = 'cpepai/generate-post';

        // Forward parameters directly to backend (already shaped per backend schema)
        $request_data = $parameters;

        // Make authenticated request to backend API
        // Credentials will be fetched internally by make_authenticated_request
        $response = $this->make_authenticated_request($endpoint, 'POST', $request_data);

        // Log the request (always log, including failures)
        if ($this->logging_service !== null) {
            $task_id = null;
            if (isset($response['data']) && is_array($response['data'])) {
                // Try nested path first: $response['data']['data']['taskId']
                if (isset($response['data']['data']['taskId'])) {
                    $task_id = $response['data']['data']['taskId'];
                } elseif (isset($response['data']['taskId'])) {
                    $task_id = $response['data']['taskId'];
                }
            }

            // Calculate tokens spent only if request succeeded: 1 token per section in structure
            $tokens_spent = null;
            if ($response['success'] && isset($parameters['structure']) && is_array($parameters['structure'])) {
                $tokens_spent = count($parameters['structure']) * 1; // TOKENS_PER_SECTION_POST_GENERATION = 1
            }

            $level = $response['success'] ? 'info' : 'error';

            // Build error message with moderation context if available
            $error_message = null;
            if (!$response['success']) {
                $error_message = $response['error'] ?? 'Unknown error';

                // Check if this is a moderation/blocked user error
                $status_code = $response['status_code'] ?? null;
                if ($status_code === 403) {
                    // Check if response data contains moderation information
                    if (isset($response['data']['message'])) {
                        $error_message = $response['data']['message'];
                    } elseif (isset($response['data']['error'])) {
                        $error_message = $response['data']['error'];
                    }

                    // Check for moderation-specific error codes or messages
                    if (isset($response['data']['reason'])) {
                        $error_message .= ' (Reason: ' . $response['data']['reason'] . ')';
                    }
                }
            }

            $message = $response['success']
                ? 'Post generation request completed successfully'
                : 'Post generation request failed: ' . ($error_message ?? 'Unknown error');

            $context = [
                'endpoint' => $endpoint,
                'status_code' => $response['status_code'] ?? null,
                'success' => $response['success']
            ];

            if ($tokens_spent !== null) {
                $context['tokens_spent'] = $tokens_spent;
            }

            if (!$response['success']) {
                if ($error_message) {
                    $context['error'] = $error_message;
                }
                // Include full response data for debugging moderation failures
                if (isset($response['data']) && is_array($response['data'])) {
                    $context['response_data'] = $response['data'];
                }
            }

            $this->logging_service->write_to_log_table(
                $level,
                'post_generation',
                $message,
                $context,
                $task_id,
                null,
                null,
                $tokens_spent
            );
        }

        return $response;
    }

    /**
     * Get usage data from the backend API.
     *
     * @return array The response data containing usage information (blocked, tokensRemaining, renewDate).
     */
    public function get_usage_data(): array
    {
        $endpoint = 'usage';

        // Make authenticated GET request to backend API
        // For '/usage' endpoint, Express req.path is '/' (relative to router mount)
        // So we need to use '/' as the HMAC path instead of '/usage'
        // Credentials will be fetched internally by make_authenticated_request
        $response = $this->make_authenticated_request($endpoint, 'GET', [], '', '', '/');

        return $response;
    }

    /**
     * Get task state from the backend API.
     *
     * @param string $task_id The task ID to fetch state for.
     * @return array The response data containing task state (status, result, timestamps, etc.).
     */
    public function get_task_state(string $task_id): array
    {
        $endpoint = 'cpepai/task/' . urlencode($task_id);

        // Make authenticated GET request to backend API
        // For '/cpepai/task/:taskId' endpoint, Express req.path is '/task/:taskId' (relative to router mount)
        // So we need to use '/task/:taskId' as the HMAC path
        // Credentials will be fetched internally by make_authenticated_request
        $hmac_path = '/task/' . urlencode($task_id);
        $response = $this->make_authenticated_request($endpoint, 'GET', [], '', '', $hmac_path);

        return $response;
    }
}
