<?php
/**
 * CLI command to test all AI agent workflows end-to-end.
 *
 * @package SAAI
 */

namespace SAAI\Cli;

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

defined( 'ABSPATH' ) || exit;

/**
 * Test_Agents class.
 *
 * Runs each AI agent workflow against real WooCommerce objects to verify
 * the full client → server → AI → response pipeline works.
 *
 * @since 1.1.5
 */
class Test_Agents {

    /**
     * Agent registry mapping agent keys to their configuration.
     *
     * Each entry defines the class, the type of WC object needed,
     * and how to call the agent.
     *
     * @since 1.1.5
     * @return array
     */
    private function get_agent_registry() {
        return array(
            'product-description'       => array(
                'class'       => 'SAAI\AI_Agents\Product_Description_AI',
                'object_type' => 'product',
                'call_type'   => 'run_agent_query',
                'param_key'   => 'product_id',
            ),
            'product-tags-generator'    => array(
                'class'       => 'SAAI\AI_Agents\Product_Tags_Generator_AI',
                'object_type' => 'product',
                'call_type'   => 'run_agent_query',
                'param_key'   => 'product_id',
            ),
            'product-summaries'         => array(
                'class'       => 'SAAI\AI_Agents\Product_Summaries_AI',
                'object_type' => 'product',
                'call_type'   => 'run_agent_query',
                'param_key'   => 'product_id',
            ),
            'review-summaries'          => array(
                'class'       => 'SAAI\AI_Agents\Review_Summaries_AI',
                'object_type' => 'product',
                'call_type'   => 'run_agent_query',
                'param_key'   => 'product_id',
            ),
            'product-upsells'           => array(
                'class'       => 'SAAI\AI_Agents\Product_Upsells_AI',
                'object_type' => 'product',
                'call_type'   => 'run_agent_query',
                'param_key'   => 'product_id',
            ),
            'vendors-product-moderator' => array(
                'class'       => 'SAAI\AI_Agents\Vendors\Vendors_Product_Moderator_AI',
                'object_type' => 'product',
                'call_type'   => 'run_agent_query',
                'param_key'   => 'product_id',
                'requires'    => 'wc-vendors',
            ),
            'image-alt-tags'            => array(
                'class'       => 'SAAI\AI_Agents\Image_Alt_Tags_AI',
                'object_type' => 'attachment',
                'call_type'   => 'run_agent_query',
                'param_key'   => 'attachment_id',
            ),
            'coupon-description'        => array(
                'class'       => 'SAAI\AI_Agents\Coupon_Description_AI',
                'object_type' => 'coupon',
                'call_type'   => 'positional',
            ),
            'review-moderation'         => array(
                'class'       => 'SAAI\AI_Agents\Review_Moderation_AI',
                'object_type' => 'comment',
                'call_type'   => 'run_agent_query',
                'param_key'   => 'comment_id',
            ),
            'category-description'      => array(
                'class'       => 'SAAI\AI_Agents\Category_Description_AI',
                'object_type' => 'category',
                'call_type'   => 'direct',
            ),
        );
    }

    /**
     * Run the test-agents command.
     *
     * @since 1.1.5
     * @param array $args       Positional arguments. First element is optional agent name.
     * @param array $assoc_args Associative arguments (--product-id, --coupon-id, etc.).
     */
    public function run( $args = array(), $assoc_args = array() ) {

        // Check connection first.
        if ( ! Connect::is_connected() ) {
            \WP_CLI::error( 'StoreAgent is not connected. Run the onboarding wizard first.' );
            return;
        }

        $dry_run   = isset( $assoc_args['dry-run'] );
        $skip_save = isset( $assoc_args['skip-save'] ) || $dry_run;
        $registry  = $this->get_agent_registry();

        // Filter to single agent if specified.
        $single_agent = ! empty( $args[0] ) ? $args[0] : null;
        if ( $single_agent ) {
            if ( ! isset( $registry[ $single_agent ] ) ) {
                \WP_CLI::error(
                    sprintf(
                        'Unknown agent "%s". Available: %s',
                        $single_agent,
                        implode( ', ', array_keys( $registry ) )
                    )
                );
                return;
            }
            $registry = array( $single_agent => $registry[ $single_agent ] );
        }

        // Discover test objects.
        $test_objects = $this->discover_test_objects( $assoc_args );

        \WP_CLI::line( '' );
        \WP_CLI::line( '=== StoreAgent AI Agent Test Suite ===' );
        \WP_CLI::line( '' );

        $this->print_test_objects( $test_objects );
        \WP_CLI::line( '' );

        if ( $dry_run ) {
            \WP_CLI::success( 'Dry run complete. Test objects discovered above.' );
            return;
        }

        // Run tests.
        $results = array();
        foreach ( $registry as $agent_key => $config ) {
            $results[ $agent_key ] = $this->run_single_test( $agent_key, $config, $test_objects, $skip_save );
        }

        // Print summary.
        $this->print_summary( $results );
    }

    /**
     * Discover test objects from flags or auto-detect from the database.
     *
     * @since 1.1.5
     * @param array $assoc_args CLI associative arguments.
     * @return array Map of object types to their IDs and labels.
     */
    private function discover_test_objects( $assoc_args ) {
        $objects = array();

        // Product.
        if ( ! empty( $assoc_args['product-id'] ) ) {
            $product = wc_get_product( intval( $assoc_args['product-id'] ) );
            if ( $product ) {
                $objects['product'] = array(
                    'id'    => $product->get_id(),
                    'label' => $product->get_name(),
                );
            }
        } else {
            $products = wc_get_products(
                array(
                    'limit'  => 1,
                    'status' => 'publish',
                )
            );
            if ( ! empty( $products ) ) {
                $objects['product'] = array(
                    'id'    => $products[0]->get_id(),
                    'label' => $products[0]->get_name(),
                );
            }
        }

        // Coupon.
        if ( ! empty( $assoc_args['coupon-id'] ) ) {
            $coupon = new \WC_Coupon( intval( $assoc_args['coupon-id'] ) );
            if ( $coupon->get_id() ) {
                $objects['coupon'] = array(
                    'id'    => $coupon->get_id(),
                    'label' => $coupon->get_code(),
                );
            }
        } else {
            $coupons = get_posts(
                array(
                    'post_type'   => 'shop_coupon',
                    'numberposts' => 1,
                    'post_status' => 'publish',
                )
            );
            if ( ! empty( $coupons ) ) {
                $objects['coupon'] = array(
                    'id'    => $coupons[0]->ID,
                    'label' => $coupons[0]->post_title,
                );
            }
        }

        // Comment (review).
        if ( ! empty( $assoc_args['comment-id'] ) ) {
            $comment = get_comment( intval( $assoc_args['comment-id'] ) );
            if ( $comment ) {
                $objects['comment'] = array(
                    'id'    => intval( $comment->comment_ID ),
                    'label' => substr( $comment->comment_content, 0, 50 ),
                );
            }
        } else {
            $comments = get_comments(
                array(
                    'type'   => 'review',
                    'status' => 'approve',
                    'number' => 1,
                )
            );
            if ( ! empty( $comments ) ) {
                $objects['comment'] = array(
                    'id'    => intval( $comments[0]->comment_ID ),
                    'label' => substr( $comments[0]->comment_content, 0, 50 ),
                );
            }
        }

        // Attachment (image).
        if ( ! empty( $assoc_args['attachment-id'] ) ) {
            $attachment = get_post( intval( $assoc_args['attachment-id'] ) );
            if ( $attachment && 'attachment' === $attachment->post_type ) {
                $objects['attachment'] = array(
                    'id'    => $attachment->ID,
                    'label' => $attachment->post_title,
                );
            }
        } else {
            $attachments = get_posts(
                array(
                    'post_type'      => 'attachment',
                    'post_mime_type' => 'image',
                    'numberposts'    => 1,
                    'post_status'    => 'inherit',
                )
            );
            if ( ! empty( $attachments ) ) {
                $objects['attachment'] = array(
                    'id'    => $attachments[0]->ID,
                    'label' => $attachments[0]->post_title,
                );
            }
        }

        // Category.
        if ( ! empty( $assoc_args['category-id'] ) ) {
            $term = get_term( intval( $assoc_args['category-id'] ), 'product_cat' );
            if ( $term && ! is_wp_error( $term ) ) {
                $objects['category'] = array(
                    'id'    => $term->term_id,
                    'label' => $term->name,
                );
            }
        } else {
            $terms = get_terms(
                array(
                    'taxonomy'   => 'product_cat',
                    'number'     => 1,
                    'hide_empty' => false,
                    'exclude'    => array( get_option( 'default_product_cat', 0 ) ),
                )
            );
            if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
                $objects['category'] = array(
                    'id'    => $terms[0]->term_id,
                    'label' => $terms[0]->name,
                );
            }
        }

        return $objects;
    }

    /**
     * Print discovered test objects.
     *
     * @since 1.1.5
     * @param array $test_objects Map of object types to their IDs and labels.
     */
    private function print_test_objects( $test_objects ) {
        \WP_CLI::line( 'Test objects:' );

        $type_labels = array(
            'product'    => 'Product',
            'coupon'     => 'Coupon',
            'comment'    => 'Review',
            'attachment' => 'Image',
            'category'   => 'Category',
        );

        foreach ( $type_labels as $type => $label ) {
            if ( isset( $test_objects[ $type ] ) ) {
                \WP_CLI::line(
                    sprintf(
                        '  %s: #%d — %s',
                        str_pad( $label, 10 ),
                        $test_objects[ $type ]['id'],
                        $test_objects[ $type ]['label']
                    )
                );
            } else {
                \WP_CLI::line( sprintf( '  %s: (not found)', str_pad( $label, 10 ) ) );
            }
        }
    }

    /**
     * Run a single agent test.
     *
     * @since 1.1.5
     * @param string $agent_key    The agent registry key.
     * @param array  $config       The agent registry config.
     * @param array  $test_objects  Discovered test objects.
     * @param bool   $skip_save    Whether to skip save_object_data.
     * @return array Result with 'status', 'message', and 'duration' keys.
     */
    private function run_single_test( $agent_key, $config, $test_objects, $skip_save ) {
        \WP_CLI::line( sprintf( 'Testing %s...', $agent_key ) );

        $start = microtime( true );

        // Check plugin requirements.
        // Currently only WC Vendors is supported. The requires key and class check are coupled intentionally.
        if ( ! empty( $config['requires'] ) && 'wc-vendors' === $config['requires'] ) {
            if ( ! class_exists( 'WC_Vendors' ) ) {
                $duration = microtime( true ) - $start;
                $this->print_result( $agent_key, 'skip', 'WC Vendors not active', $duration );
                return array(
                    'status'   => 'skip',
                    'message'  => 'WC Vendors not active',
                    'duration' => $duration,
                );
            }
        }

        // Check test object availability.
        $object_type = $config['object_type'];
        if ( ! isset( $test_objects[ $object_type ] ) ) {
            $duration = microtime( true ) - $start;
            $this->print_result( $agent_key, 'skip', "No {$object_type} found", $duration );
            return array(
                'status'   => 'skip',
                'message'  => "No {$object_type} found",
                'duration' => $duration,
            );
        }

        $object_id = $test_objects[ $object_type ]['id'];

        // Check class exists.
        if ( ! class_exists( $config['class'] ) ) {
            $duration = microtime( true ) - $start;
            $this->print_result( $agent_key, 'fail', "Class {$config['class']} not found", $duration );
            return array(
                'status'   => 'fail',
                'message'  => "Class {$config['class']} not found",
                'duration' => $duration,
            );
        }

        $agent = $config['class']::instance();

        // Execute the agent.
        try {
            $response = $this->call_agent( $agent, $config, $object_id, $test_objects );
        } catch ( \Exception $e ) {
            $duration = microtime( true ) - $start;
            $this->print_result( $agent_key, 'fail', $e->getMessage(), $duration );
            return array(
                'status'   => 'fail',
                'message'  => $e->getMessage(),
                'duration' => $duration,
            );
        }

        if ( is_wp_error( $response ) ) {
            $duration = microtime( true ) - $start;
            $this->print_result( $agent_key, 'fail', $response->get_error_message(), $duration );
            return array(
                'status'   => 'fail',
                'message'  => $response->get_error_message(),
                'duration' => $duration,
            );
        }

        // Detect worker-format errors (success: false, error: { code, message }).
        if ( is_array( $response ) && isset( $response['success'] ) && false === $response['success'] ) {
            $error_msg = isset( $response['error']['message'] ) ? $response['error']['message'] : 'Unknown server error';
            $duration  = microtime( true ) - $start;
            $this->print_result( $agent_key, 'fail', $error_msg, $duration );
            return array(
                'status'   => 'fail',
                'message'  => $error_msg,
                'duration' => $duration,
            );
        }

        // Validate response is not empty.
        // Some agents return ['completion' => data], others return the completion directly.
        if ( empty( $response ) ) {
            $duration = microtime( true ) - $start;
            $this->print_result( $agent_key, 'fail', 'Empty response', $duration );
            return array(
                'status'   => 'fail',
                'message'  => 'Empty response',
                'duration' => $duration,
            );
        }

        // Optionally save.
        if ( ! $skip_save && method_exists( $agent, 'save_object_data' ) ) {
            $completion  = is_array( $response ) && isset( $response['completion'] ) ? $response['completion'] : $response;
            $save_result = $agent->save_object_data( $object_id, $completion );
            if ( is_wp_error( $save_result ) ) {
                $duration = microtime( true ) - $start;
                $this->print_result( $agent_key, 'fail', 'Save failed: ' . $save_result->get_error_message(), $duration );
                return array(
                    'status'   => 'fail',
                    'message'  => 'Save failed: ' . $save_result->get_error_message(),
                    'duration' => $duration,
                );
            }
        }

        $duration = microtime( true ) - $start;
        $this->print_result( $agent_key, 'pass', 'OK', $duration );
        return array(
            'status'   => 'pass',
            'message'  => 'OK',
            'duration' => $duration,
        );
    }

    /**
     * Call an agent based on its call_type configuration.
     *
     * @since 1.1.5
     * @param object $agent        The agent instance.
     * @param array  $config       The agent registry config.
     * @param int    $object_id    The test object ID.
     * @param array  $test_objects All discovered test objects.
     * @return array|WP_Error The agent response.
     */
    private function call_agent( $agent, $config, $object_id, $test_objects ) {

        switch ( $config['call_type'] ) {
            case 'run_agent_query':
                return $agent->run_agent_query( array( $config['param_key'] => $object_id ) );

            case 'positional':
                // Coupon_Description_AI uses positional param.
                return $agent->run_agent_query( $object_id );

            case 'direct':
                // Category_Description_AI has no run_agent_query. Call set_data + send_prompt directly.
                return $this->call_category_description( $agent, $test_objects );

            default:
                return new WP_Error( 'unknown_call_type', "Unknown call_type: {$config['call_type']}" );
        }
    }

    /**
     * Call the category description agent directly via set_data + send_prompt.
     *
     * @since 1.1.5
     * @param object $agent        The Category_Description_AI instance.
     * @param array  $test_objects Discovered test objects.
     * @return array|WP_Error The agent response.
     */
    private function call_category_description( $agent, $test_objects ) {

        $category = $test_objects['category'];
        $term     = get_term( $category['id'], 'product_cat' );

        if ( ! $term || is_wp_error( $term ) ) {
            return new WP_Error( 'term_not_found', 'Category term not found.' );
        }

        $data = array(
            'category_name'      => $term->name,
            'language'           => Helper::resolve_default_language(),
            'description_length' => 'medium',
        );

        $agent->set_data( $data );
        $response = $agent->send_prompt();

        if ( is_wp_error( $response ) ) {
            return $response;
        }

        // Wrap in completion key for consistent validation.
        if ( isset( $response['completion'] ) ) {
            return $response;
        }

        return array( 'completion' => $response );
    }

    /**
     * Print a single test result line.
     *
     * @since 1.1.5
     * @param string $agent_key Agent registry key.
     * @param string $status    One of 'pass', 'fail', 'skip'.
     * @param string $message   Result message.
     * @param float  $duration  Duration in seconds.
     */
    private function print_result( $agent_key, $status, $message, $duration ) {
        $icons = array(
            'pass' => "\033[32m✓\033[0m",
            'fail' => "\033[31m✗\033[0m",
            'skip' => "\033[33m⊘\033[0m",
        );

        $icon = isset( $icons[ $status ] ) ? $icons[ $status ] : '?';
        \WP_CLI::line(
            sprintf(
                '  %s %s — %.1fs (%s)',
                $icon,
                str_pad( $agent_key, 30 ),
                $duration,
                $message
            )
        );
    }

    /**
     * Print the test summary.
     *
     * @since 1.1.5
     * @param array $results Map of agent keys to result arrays.
     */
    private function print_summary( $results ) {
        $passed  = 0;
        $failed  = 0;
        $skipped = 0;

        foreach ( $results as $result ) {
            switch ( $result['status'] ) {
                case 'pass':
                    ++$passed;
                    break;
                case 'fail':
                    ++$failed;
                    break;
                case 'skip':
                    ++$skipped;
                    break;
            }
        }

        $total = count( $results );
        \WP_CLI::line( '' );
        \WP_CLI::line( str_repeat( '-', 50 ) );

        $summary = sprintf( 'Summary: %d/%d passed', $passed, $total );
        if ( $failed > 0 ) {
            $summary .= sprintf( ', %d failed', $failed );
        }
        if ( $skipped > 0 ) {
            $summary .= sprintf( ', %d skipped', $skipped );
        }

        if ( $failed > 0 ) {
            \WP_CLI::error( $summary, false );
        } else {
            \WP_CLI::success( $summary );
        }
    }
}
