<?php
/**
 * Author: Rymera Web Co.
 *
 * @package SAAI\Classes
 */

namespace SAAI\Classes;

use SAAI\Helpers\Connect;
use SAAI\Helpers\Helper;
use SAAI\Traits\Singleton_Trait;
use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * Plan_Manager class.
 *
 * Manages subscription plan fetching, storage, and feature availability
 * for the StoreAgent AI plugin.
 *
 * @since 1.0.7
 */
class Plan_Manager {

    use Singleton_Trait;

    /**
     * Holds the class instance object
     *
     * @var Plan_Manager $instance object
     * @since 1.0.7
     */
    protected static $instance;

    /**
     * WordPress option keys for plan data storage.
     */
    const OPTION_PLANS_DATA        = 'saai_subscription_plans_data';
    const OPTION_LAST_FETCH        = 'saai_subscription_plans_last_fetch';
    const OPTION_RETRY_COUNT       = 'saai_subscription_plans_retry_count';
    const OPTION_FEATURES_DISABLED = 'saai_subscription_features_disabled';

    /**
     * Action Scheduler hooks.
     */
    const HOOK_DAILY_FETCH = 'saai_fetch_subscription_plans_daily';
    const HOOK_RETRY_FETCH = 'saai_retry_subscription_plans_fetch';

    /**
     * Configuration constants.
     */
    const MAX_RETRY_COUNT = 3;
    const RETRY_INTERVAL  = HOUR_IN_SECONDS;
    const FETCH_INTERVAL  = DAY_IN_SECONDS;

    /**
     * Plan name hierarchy for upgrade CTA.
     *
     * @since 1.3.0
     */
    const PLAN_HIERARCHY = array( 'lite', 'growth', 'pro', 'business' );

    /**
     * Run the Plan Manager.
     *
     * @since 1.0.7
     * @return void
     */
    public function run() {
        add_action( 'admin_init', array( $this, 'fetch_subscription_plans_after_connection' ) );
        add_action( self::HOOK_DAILY_FETCH, array( $this, 'fetch_subscription_plans' ) );
        add_action( self::HOOK_RETRY_FETCH, array( $this, 'retry_fetch_subscription_plans' ) );
    }

    /**
     * Schedule the daily fetch of subscription plans.
     *
     * @since 1.0.7
     * @return void
     */
    public function schedule_daily_fetch() {
        if ( ! function_exists( 'as_has_scheduled_action' ) ) {
            return;
        }

        // Only schedule if not already scheduled.
        if ( ! as_has_scheduled_action( self::HOOK_DAILY_FETCH ) ) {
            // Randomize start time within next 24 hours.
            $next_run = time() + wp_rand( 0, DAY_IN_SECONDS );
            as_schedule_recurring_action( $next_run, self::FETCH_INTERVAL, self::HOOK_DAILY_FETCH, array(), 'storeagent-plans' );
        }
    }

    /**
     * Unschedule all Plan Manager tasks.
     *
     * @since 1.0.7
     * @return void
     */
    public function unschedule_tasks() {
        if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
            return;
        }

        // Unschedule daily fetch.
        as_unschedule_all_actions( self::HOOK_DAILY_FETCH, array(), 'storeagent-plans' );

        // Unschedule retry fetch.
        as_unschedule_all_actions( self::HOOK_RETRY_FETCH, array(), 'storeagent-plans' );
    }

    /**
     * Cleanup all plan data and schedules on deactivation.
     * This is a more complete cleanup than just unscheduling tasks.
     *
     * @since 1.0.7
     * @return void
     */
    public function cleanup_on_deactivation() {
        $this->unschedule_tasks();
    }

    /**
     * Fetch subscription plans after connection.
     *
     * @since 1.0.7
     */
    public function fetch_subscription_plans_after_connection() {
        // If the store is not connected, skip.
        if ( ! Connect::is_connected() ) {
            return;
        }

        // If the plan data is already fetched, skip.
        $raw_data = get_option( self::OPTION_PLANS_DATA, false );
        if ( ! empty( $raw_data ) ) {
            return;
        }

        // Fetch the subscription plans.
        $this->fetch_subscription_plans();
    }

    /**
     * Fetch subscription plans from the remote server.
     *
     * @since 1.0.7
     * @return bool|WP_Error True on success, WP_Error on failure.
     */
    public function fetch_subscription_plans() {
        // Check if we're connected.
        if ( ! Connect::is_connected() ) {
            return new WP_Error( 'not_connected', __( 'StoreAgent is not connected.', 'storeagent-ai-for-woocommerce' ) );
        }

        $api_url = trailingslashit( Helper::get_storeagent_base_url( 'api' ) ) . 'storeagent/v1/plan';
        $headers = array(
            'Content-Type'  => 'application/json',
            'X-Origin'      => wp_parse_url( get_home_url(), PHP_URL_HOST ),
            'Authorization' => 'Basic ' . base64_encode( Connect::get_consumer_key() . ':' . Connect::get_consumer_secret() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
        );

        $response = wp_remote_get(
            $api_url,
            array(
                'headers' => $headers,
                'timeout' => 30,
            )
        );

        if ( is_wp_error( $response ) ) {
            $this->handle_fetch_error( $response );
            return $response;
        }

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

        if ( 200 !== $status_code ) {
            $error_data = json_decode( $body, true );
            $message    = isset( $error_data['message'] ) ? $error_data['message'] : __( 'Failed to fetch subscription plans.', 'storeagent-ai-for-woocommerce' );
            $error      = new WP_Error( 'fetch_failed', $message, array( 'status' => $status_code ) );
            $this->handle_fetch_error( $error );
            return $error;
        }

        $plan_data = json_decode( $body, true );

        if ( ! is_array( $plan_data ) ) {
            $error = new WP_Error( 'invalid_response', __( 'Invalid response format from plans server.', 'storeagent-ai-for-woocommerce' ) );
            $this->handle_fetch_error( $error );
            return $error;
        }

        // Store the fetched data.
        $this->store_plans_data( $plan_data );
        $this->reset_retry_count();

        return true;
    }

    /**
     * Handle fetch errors and schedule retries.
     *
     * @since 1.0.7
     * @param WP_Error $error The error object.
     * @return void
     */
    private function handle_fetch_error( $error ) {
        // Log the error.
        wc_get_logger()->error(
            'Failed to fetch subscription plans: ' . $error->get_error_message(),
            array( 'source' => 'storeagent-ai-for-woocommerce' )
        );

        // Schedule retry if under max attempts.
        if ( $this->get_retry_count() < self::MAX_RETRY_COUNT ) {
            $this->schedule_retry();
        }
    }

    /**
     * Store plans data in WordPress options.
     *
     * @since 1.0.7
     * @param array $plan_data The plan data to store.
     * @return void
     */
    private function store_plans_data( $plan_data ) {
        // Add timestamp to the data.
        $data_with_timestamp = array(
            'plan'       => $plan_data,
            'fetched_at' => time(),
        );

        update_option( self::OPTION_PLANS_DATA, $data_with_timestamp, false );
        update_option( self::OPTION_LAST_FETCH, time(), false );

        // If limits are no longer exceeded, clear plan limit notice flags.
        if ( ! $this->is_plan_limit_exceeded() ) {
            delete_option( Plan_Limit_Notice::DISMISS_OPTION );
            delete_option( Plan_Limit_Notice::NOTIFICATION_CREATED_OPTION );
        }
    }

    /**
     * Reset the retry count.
     *
     * @since 1.0.7
     * @return void
     */
    private function reset_retry_count() {
        delete_option( self::OPTION_RETRY_COUNT );
    }

    /**
     * Check if plan limits are exceeded.
     *
     * Checks ai_actions and chat_messages usage against limits.
     * Matches server-side logic in Plans_Permission (excludes vector_items).
     *
     * @since 1.1.0
     * @return bool True if any limit is exceeded.
     */
    public function is_plan_limit_exceeded() {
        $plan_data = $this->get_plan_data();
        $limits    = $plan_data['limits'] ?? array();
        $usage     = $plan_data['usage'] ?? array();

        // Check ai_actions: exceeded if limit > 0 AND usage >= limit.
        $ai_limit = $limits['ai_actions'] ?? 0;
        $ai_usage = $usage['ai_actions'] ?? 0;
        if ( $ai_limit > 0 && $ai_usage >= $ai_limit ) {
            return true;
        }

        // Check chat_messages: exceeded if limit > 0 AND usage >= limit.
        $chat_limit = $limits['chat_messages'] ?? 0;
        $chat_usage = $usage['chat_messages'] ?? 0;
        if ( $chat_limit > 0 && $chat_usage >= $chat_limit ) {
            return true;
        }

        return false;
    }

    /**
     * Check if chat is allowed.
     *
     * @since 1.0.7
     * @return bool True if chat is allowed, false otherwise.
     */
    public function is_chat_allowed() {
        $current_plan = $this->get_plan_data();
        return $current_plan['limits']['chat_messages'] > 0;
    }

    /**
     * Check if vector is allowed.
     *
     * @since 1.0.7
     * @return bool True if vector is allowed, false otherwise.
     */
    public function is_vector_allowed() {
        $current_plan = $this->get_plan_data();
        return $current_plan['limits']['vector_items'] > 0;
    }

    /**
     * Check if vector upload is allowed.
     *
     * @since 1.0.9
     * @return bool True if vector upload is allowed, false otherwise.
     */
    public function is_vector_upload_allowed() {
        $current_plan = $this->get_plan_data();
        $limit        = $current_plan['limits']['vector_items'] ?? 0;
        $used         = $current_plan['usage']['vector_items'] ?? 0;

        return $limit > 0 && $used < $limit;
    }

    /**
     * Check if actions are allowed.
     *
     * @since 1.0.7
     * @return bool True if actions are allowed, false otherwise.
     */
    public function is_ai_actions_allowed() {
        $current_plan = $this->get_plan_data();
        return $current_plan['limits']['ai_actions'] > 0;
    }

    /**
     * Get the retry count.
     *
     * @since 1.0.7
     * @return int The current retry count.
     */
    private function get_retry_count() {
        return (int) get_option( self::OPTION_RETRY_COUNT, 0 );
    }

    /**
     * Schedule a retry fetch.
     *
     * @since 1.0.7
     * @return void
     */
    private function schedule_retry() {
        if ( ! function_exists( 'as_schedule_single_action' ) ) {
            return;
        }

        // Cancel any existing retry action.
        if ( function_exists( 'as_unschedule_all_actions' ) ) {
            as_unschedule_all_actions( self::HOOK_RETRY_FETCH, array(), 'storeagent-plans' );
        }

        // Schedule retry in 1 hour.
        as_schedule_single_action( time() + self::RETRY_INTERVAL, self::HOOK_RETRY_FETCH, array(), 'storeagent-plans' );
    }

    /**
     * Get stored plan data.
     *
     * @since 1.0.7
     * @return array The plan data or false if not found.
     */
    public function get_plan_data() {
        $stored_data = get_option( self::OPTION_PLANS_DATA, false );
        $stored_data = $stored_data['plan'] ?? array();

        return $this->parse_plan_data( $stored_data );
    }

    /**
     * Parse plan data.
     *
     * @since 1.0.7
     * @param array $data The plan data.
     * @return array The parsed plan data.
     */
    private function parse_plan_data( $data ) {
        // Parse plan data.
        $data = wp_parse_args(
            $data,
            array(
                'account_email' => '',
                'plan_name'     => '',
                'status'        => '',
                'next_renewal'  => null,
                'created_date'  => '',
            )
        );

        // Parse limits.
        $limits         = $data['limits'] ?? array();
        $data['limits'] = array(
            'ai_actions'    => $limits['ai_actions_limit'] ?? 0,
            'vector_items'  => $limits['vector_items_limit'] ?? 0,
            'chat_messages' => $limits['chat_messages_limit'] ?? 0,
        );

        // Parse usage.
        $usage         = $data['usage'] ?? array();
        $data['usage'] = array(
            'ai_actions'    => $usage['ai_actions_limit'] ?? 0,
            'vector_items'  => $usage['vector_items_limit'] ?? 0,
            'chat_messages' => $usage['chat_messages_limit'] ?? 0,
        );

        // Ensure next_renewal is properly structured.
        if ( isset( $data['next_renewal'] ) && is_array( $data['next_renewal'] ) ) {
            $data['next_renewal'] = wp_parse_args(
                $data['next_renewal'],
                array(
                    'date'      => '',
                    'time_diff' => '',
                    'raw_date'  => '',
                )
            );
        }

        return $data;
    }

    /**
     * Get the limit for a given property.
     *
     * @since 1.0.7
     * @param string $property The property to get the limit for.
     * @return int The limit for the property.
     */
    public function get_plan_limit( $property ) {
        $current_plan = $this->get_plan_data();
        return $current_plan['limits'][ $property ] ?? 0;
    }

    /**
     * Get the usage count for a given property.
     *
     * @since 1.0.7
     * @param string $property The property to get the usage count for.
     * @return int The usage count for the property.
     */
    public function get_usage_count( $property ) {
        $current_plan = $this->get_plan_data();
        return $current_plan['usage'][ $property ] ?? 0;
    }

    /**
     * Retry fetching subscription plans after a failure.
     *
     * @since 1.0.7
     * @return void
     */
    public function retry_fetch_subscription_plans() {
        $retry_count = $this->get_retry_count();

        if ( $retry_count >= self::MAX_RETRY_COUNT ) {
            wc_get_logger()->error( 'Max retry attempts reached for subscription plans fetch.', array( 'source' => 'storeagent-ai-for-woocommerce' ) );
            return;
        }

        $result = $this->fetch_subscription_plans();

        if ( is_wp_error( $result ) ) {
            // Increment retry count and schedule another retry.
            $this->increment_retry_count();
        }
    }

    /**
     * Increment the retry count.
     *
     * @since 1.0.7
     * @return void
     */
    private function increment_retry_count() {
        $count = $this->get_retry_count() + 1;
        update_option( self::OPTION_RETRY_COUNT, $count, false );
    }

    /**
     * Get the last fetch timestamp.
     *
     * @since 1.0.7
     * @return int|false The last fetch timestamp or false if not set.
     */
    public function get_last_fetch_time() {
        return get_option( self::OPTION_LAST_FETCH, false );
    }

    /**
     * Manually trigger a fetch of subscription plans.
     *
     * @since 1.0.7
     * @return bool|WP_Error True on success, WP_Error on failure.
     */
    public function manual_fetch() {
        return $this->fetch_subscription_plans();
    }

    /**
     * Clear all stored plan data.
     *
     * @since 1.0.7
     * @return void
     */
    public function clear_plan_data() {
        delete_option( self::OPTION_PLANS_DATA );
        delete_option( self::OPTION_LAST_FETCH );
        delete_option( self::OPTION_RETRY_COUNT );
    }

    /**
     * Get the resource with the highest usage percentage.
     *
     * Returns the most-consumed resource among AI Content Tasks,
     * Chat Messages, and Memory Items for the usage progress bar.
     *
     * Keys use camelCase because this array is JSON-encoded into `saaiObj.usageData`
     * and consumed directly by the Vue `UsageIndicator` component.
     *
     * @since 1.3.0
     * @return array {
     *     @type string $resource     Display name of the resource.
     *     @type int    $used         Current usage count.
     *     @type int    $limit        Plan limit for the resource.
     *     @type float  $percentage   Usage percentage (0-100).
     *     @type string $nextPlanName Display name of the next upgrade plan, or empty if highest/unknown.
     * }
     */
    public function get_highest_usage_resource() {
        $plan_data = $this->get_plan_data();
        $limits    = $plan_data['limits'] ?? array();
        $usage     = $plan_data['usage'] ?? array();
        $plan_name = $this->normalize_plan_name( $plan_data['plan_name'] ?? '' );

        $highest = array(
            'resource'     => '',
            'used'         => 0,
            'limit'        => 0,
            'percentage'   => 0,
            'nextPlanName' => '',
        );

        // Without a plan name we cannot determine the upgrade target, so leave nextPlanName empty.
        if ( '' === $plan_name ) {
            return $highest;
        }

        $highest['nextPlanName'] = $this->get_next_plan_name( $plan_name );

        // Define tracked resources with display names.
        $resources = array(
            'ai_actions'    => __( 'AI Tasks', 'storeagent-ai-for-woocommerce' ),
            'chat_messages' => __( 'Chat Messages', 'storeagent-ai-for-woocommerce' ),
            'vector_items'  => __( 'Memory Items', 'storeagent-ai-for-woocommerce' ),
        );

        $max_percentage = -1;

        foreach ( $resources as $key => $label ) {
            $limit = $limits[ $key ] ?? 0;
            $used  = $usage[ $key ] ?? 0;

            // Skip resources with no limit (unlimited or not available).
            if ( $limit <= 0 ) {
                continue;
            }

            $percentage = min( round( ( $used / $limit ) * 100, 1 ), 100 );

            if ( $percentage > $max_percentage ) {
                $max_percentage        = $percentage;
                $highest['resource']   = $label;
                $highest['used']       = $used;
                $highest['limit']      = $limit;
                $highest['percentage'] = $percentage;
            }
        }

        return $highest;
    }

    /**
     * Normalize a plan name to its canonical lowercase form.
     *
     * Strips the trailing " plan" suffix (e.g. "Lite Plan" → "lite")
     * and lowercases the result so it can be compared against PLAN_HIERARCHY.
     *
     * @since 1.3.0
     * @param string $plan_name Raw plan name from the API.
     * @return string Normalized plan name, or empty string if input is empty.
     */
    public function normalize_plan_name( $plan_name ) {
        if ( empty( $plan_name ) ) {
            return '';
        }

        return preg_replace( '/ plan$/i', '', strtolower( $plan_name ) );
    }

    /**
     * Get the next plan name in the upgrade hierarchy.
     *
     * @since 1.3.0
     * @param string $current_plan Current plan name (lowercase).
     * @return string Display name of the next plan, or empty string if on the highest plan.
     */
    public function get_next_plan_name( $current_plan ) {
        $current_plan = $this->normalize_plan_name( $current_plan );
        $index        = array_search( $current_plan, self::PLAN_HIERARCHY, true );

        if ( false === $index || $index >= count( self::PLAN_HIERARCHY ) - 1 ) {
            return '';
        }

        return ucfirst( self::PLAN_HIERARCHY[ $index + 1 ] );
    }

    /**
     * Get plan status for admin display.
     *
     * @since 1.0.7
     * @return array Status information.
     */
    public function get_status() {
        $current_plan = $this->get_plan_data();
        $last_fetch   = $this->get_last_fetch_time();
        $retry_count  = $this->get_retry_count();

        return array(
            'has_plan'     => ! empty( $current_plan ),
            'plan_name'    => $current_plan ? ( $current_plan['plan_name'] ?? '' ) : '',
            'next_renewal' => $current_plan ? ( $current_plan['next_renewal'] ?? null ) : null,
            'created_date' => $current_plan ? ( $current_plan['created_date'] ?? '' ) : '',
            'status'       => $current_plan ? ( $current_plan['status'] ?? '' ) : '',
            'last_fetch'   => $last_fetch ? date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $last_fetch ) : __( 'Never', 'storeagent-ai-for-woocommerce' ),
            'retry_count'  => $retry_count,
            'limits'       => $current_plan['limits'] ?? array(),
            'usage'        => $current_plan['usage'] ?? array(),
        );
    }

    /**
     * Check if chat is allowed for the current user.
     *
     * @since 1.0.7
     * @return bool True if chat is allowed, false otherwise.
     */
    public function is_chat_allowed_for_current_user() {
        $current_plan = $this->get_plan_data();

        // If the plan data is not set, explicitly return false.
        if ( ! is_array( $current_plan ) || empty( $current_plan ) ) {
            return false;
        }

        return $current_plan['limits']['chat_messages'] > 0 && $current_plan['usage']['chat_messages'] < $current_plan['limits']['chat_messages'];
    }

    /**
     * Increment the locally stored AI actions usage count by 1.
     *
     * Called after a successful AI action to keep the local cache
     * in sync with the server until the next daily fetch.
     *
     * @since 1.3.0
     * @return void
     */
    public function increment_ai_actions_usage() {
        $stored_data = get_option( self::OPTION_PLANS_DATA, false );

        if ( empty( $stored_data ) || ! isset( $stored_data['plan'] ) ) {
            return;
        }

        if ( ! isset( $stored_data['plan']['usage'] ) ) {
            $stored_data['plan']['usage'] = array();
        }

        // Note: not atomic. Concurrent AI actions may under-count locally; the
        // daily server sync in fetch_subscription_plans() is authoritative and
        // corrects any drift.
        $stored_data['plan']['usage']['ai_actions_limit'] = ( $stored_data['plan']['usage']['ai_actions_limit'] ?? 0 ) + 1;

        update_option( self::OPTION_PLANS_DATA, $stored_data, false );
    }

    /**
     * Get AI actions credit usage data for REST/AJAX responses.
     *
     * @since 1.3.0
     * @return array {
     *     @type array $ai_actions {
     *         @type int $used      Current usage count.
     *         @type int $limit     Plan limit.
     *         @type int $remaining Remaining credits.
     *     }
     * }
     */
    public function get_credit_usage_response() {
        $plan_data = $this->get_plan_data();
        $limit     = $plan_data['limits']['ai_actions'] ?? 0;
        $used      = $plan_data['usage']['ai_actions'] ?? 0;
        $remaining = max( 0, $limit - $used );

        return array(
            'ai_actions' => array(
                'used'      => $used,
                'limit'     => $limit,
                'remaining' => $remaining,
            ),
        );
    }

    /**
     * Get credit usage data for frontend localization (saaiObj).
     *
     * Returns an array suitable for merging into Vite_App l10n data.
     *
     * @since 1.3.0
     * @return array Localization data with creditUsage and creditToastUpgradeUrl keys.
     */
    public static function get_credit_usage_l10n() {
        $instance  = self::instance();
        $plan_data = $instance->get_plan_data();
        $limit     = $plan_data['limits']['ai_actions'] ?? 0;
        $used      = $plan_data['usage']['ai_actions'] ?? 0;
        $plan_name = $instance->normalize_plan_name( $plan_data['plan_name'] ?? '' );

        return array(
            'creditUsage'           => array(
                'used'                    => $used,
                'limit'                   => $limit,
                'remaining'               => max( 0, $limit - $used ),
                'planName'                => $plan_name,
                'isCreditConstrainedPlan' => in_array( $plan_name, array( 'lite', 'free' ), true ),
                'nextPlanName'            => $instance->get_next_plan_name( $plan_name ),
            ),
            'creditToastUpgradeUrl' => esc_url_raw( Helper::get_utm_url( 'pricing', 'saai', 'credit-toast', 'upgrade' ) ),
        );
    }

    /**
     * Append credit usage data to a response array.
     *
     * Increments the local AI actions usage count and merges
     * the updated usage data into the provided response array.
     *
     * @since 1.3.0
     *
     * @param array $response_data The response data to augment.
     * @return array The response data with usage key added.
     */
    public function append_usage_data( $response_data ) {
        $this->increment_ai_actions_usage();
        $response_data['usage'] = $this->get_credit_usage_response();

        return $response_data;
    }
}
