<?php

namespace XZD\AI\V5\Tools;

use XZD\AI\V5\Core\Logger;

/**
 * 批量流水线处理器
 *
 * 支持三速架构：即时处理(≤5篇)、批量队列(5-200篇)、大规模(200+篇, WP-CLI + 断点续传)
 */
class BatchPipeline
{
    private const META_KEY = '_xzd_ai_batch_progress';
    private const OPTION_QUEUE = 'xzd_ai_batch_queue';
    private const BATCH_SIZE_FAST = 5;
    private const BATCH_SIZE_NORMAL = 10;
    private const BATCH_SIZE_LARGE = 25;

    /**
     * 创建批量任务
     *
     * @param array  $postIds  文章 ID 列表
     * @param array  $stages   流水线阶段
     * @param array  $options  批次配置
     * @return string batch_id
     */
    public function createBatch(array $postIds, array $stages, array $options = []): string
    {
        $batchId = 'batch_' . bin2hex(random_bytes(8));
        $batchSize = $options['batch_size'] ?? $this->detectBatchSize(count($postIds));
        $chunks = array_chunk($postIds, $batchSize);

        $meta = [
            'batch_id'      => $batchId,
            'total'         => count($postIds),
            'chunks'        => count($chunks),
            'completed'     => 0,
            'failed'        => 0,
            'stages'        => $stages,
            'status'        => 'pending',
            'progress_pct'  => 0,
            'created_at'    => current_time('mysql'),
            'started_at'    => null,
            'finished_at'   => null,
            'current_chunk' => 0,
            'current_post'  => null,
            'eta_seconds'   => 0,
            'errors'        => [],
            'options'       => $options,
            'results'       => [],
        ];

        update_option(self::OPTION_QUEUE . '_' . $batchId, [
            'chunks' => $chunks,
            'meta'   => $meta,
        ], false);

        // 初始化每篇文章的进度
        foreach ($postIds as $pid) {
            delete_post_meta($pid, self::META_KEY);
        }

        Logger::info('batch_create', "批量任务 {$batchId}", [
            'total'  => count($postIds),
            'chunks' => count($chunks),
            'stages' => $stages,
        ]);

        // 小批量直接同步执行
        if (count($postIds) <= self::BATCH_SIZE_FAST) {
            return $this->runFast($batchId);
        }

        // 大批量走队列
        $this->scheduleNext($batchId);

        return $batchId;
    }

    /**
     * 查询进度
     *
     * @return array 进度详情
     */
    public function getProgress(string $batchId): array
    {
        $stored = get_option(self::OPTION_QUEUE . '_' . $batchId, null);
        if (!$stored) {
            return ['batch_id' => $batchId, 'status' => 'not_found', 'message' => '任务不存在或已过期'];
        }

        $meta = $stored['meta'];
        $total = $meta['total'];
        $completed = $meta['completed'];
        $failed = $meta['failed'];

        // 计算 ETA
        $eta = 0;
        if ($meta['status'] === 'running' && $completed > 0 && $meta['started_at']) {
            $elapsed = time() - strtotime($meta['started_at']);
            $rate = $completed / max($elapsed, 1);
            $remaining = $total - $completed - $failed;
            $eta = $rate > 0 ? (int)($remaining / $rate) : 0;
        }

        // 汇总阶段详情
        $stageDetails = [];
        foreach (($meta['stages'] ?? []) as $stage) {
            $tool = ToolRegistry::instance()->get($stage);
            $stageDetails[$stage] = [
                'name'        => $tool ? $tool->name : $stage,
                'success'     => 0,
                'failed'      => 0,
                'avg_duration_ms' => 0,
            ];
        }
        foreach (($meta['results'] ?? []) as $r) {
            foreach (($r['stage_results'] ?? []) as $sr) {
                if (isset($stageDetails[$sr['tool_id']])) {
                    if ($sr['success']) {
                        $stageDetails[$sr['tool_id']]['success']++;
                    } else {
                        $stageDetails[$sr['tool_id']]['failed']++;
                    }
                }
            }
        }

        return [
            'batch_id'       => $batchId,
            'total'          => $total,
            'completed'      => $completed,
            'failed'         => $failed,
            'pending'        => $total - $completed - $failed,
            'progress_pct'   => $total > 0 ? round(($completed + $failed) / $total * 100, 1) : 0,
            'status'         => $meta['status'],
            'eta_seconds'    => $eta,
            'current_post'   => $meta['current_post'],
            'current_chunk'  => $meta['current_chunk'],
            'total_chunks'   => $meta['chunks'],
            'stages'         => $meta['stages'],
            'stage_details'  => $stageDetails,
            'errors'         => $meta['errors'],
            'created_at'     => $meta['created_at'],
            'started_at'     => $meta['started_at'],
            'finished_at'    => $meta['finished_at'],
        ];
    }

    /**
     * 取消批量任务
     */
    public function cancel(string $batchId): bool
    {
        $stored = get_option(self::OPTION_QUEUE . '_' . $batchId, null);
        if (!$stored) {
            return false;
        }

        $stored['meta']['status'] = 'cancelled';
        $stored['meta']['finished_at'] = current_time('mysql');
        update_option(self::OPTION_QUEUE . '_' . $batchId, $stored, false);

        // 清理 cron
        wp_clear_scheduled_hook('xzd_ai_process_batch', [$batchId]);

        Logger::info('batch_cancel', "批量任务 {$batchId} 已取消");
        return true;
    }

    /**
     * 重试失败项
     */
    public function retryFailed(string $batchId): int
    {
        $stored = get_option(self::OPTION_QUEUE . '_' . $batchId, null);
        if (!$stored) {
            return 0;
        }

        $failedPostIds = [];
        foreach (($stored['meta']['results'] ?? []) as $r) {
            if (!$r['success']) {
                $failedPostIds[] = $r['post_id'];
            }
        }

        if (empty($failedPostIds)) {
            return 0;
        }

        $stored['meta']['status'] = 'retrying';
        $stored['meta']['completed'] -= count($failedPostIds);
        $stored['meta']['failed'] -= count($failedPostIds);
        update_option(self::OPTION_QUEUE . '_' . $batchId, $stored, false);

        // 创建重试子任务
        $retryBatchId = $this->createBatch($failedPostIds, $stored['meta']['stages'], $stored['meta']['options']);

        Logger::info('batch_retry', "批量任务 {$batchId} 重试 " . count($failedPostIds) . " 篇", [
            'retry_batch_id' => $retryBatchId,
        ]);

        return count($failedPostIds);
    }

    /**
     * 定时批处理入口（WP Cron 调用）
     */
    public function processChunk(string $batchId): void
    {
        \XZD\AI\V5\Core\PreserveDates::startProtection();
        try {
            $this->doProcessChunk($batchId);
        } finally {
            \XZD\AI\V5\Core\PreserveDates::endProtection();
        }
    }

    /**
     * 内部：执行批次处理
     */
    private function doProcessChunk(string $batchId): void
    {
        $stored = get_option(self::OPTION_QUEUE . '_' . $batchId, null);
        if (!$stored || !in_array($stored['meta']['status'], ['pending', 'running', 'retrying'])) {
            return;
        }

        $meta = &$stored['meta'];
        $chunks = $stored['chunks'];
        $currentChunk = $meta['current_chunk'];

        if ($currentChunk >= count($chunks)) {
            $this->markComplete($batchId, $stored);
            return;
        }

        if ($meta['status'] === 'pending') {
            $meta['status'] = 'running';
            $meta['started_at'] = current_time('mysql');
        }

        $postIds = $chunks[$currentChunk];
        $stages = $meta['stages'];
        $parallel = new ParallelExecutor();

        foreach ($postIds as $postId) {
            $post = get_post($postId);
            if (!$post) {
                $meta['failed']++;
                $meta['results'][] = [
                    'post_id' => $postId,
                    'success' => false,
                    'message' => '文章不存在',
                ];
                continue;
            }

            $meta['current_post'] = $postId;

            // 构建工具调用列表
            $toolCalls = [];
            foreach ($stages as $stage) {
                $toolCalls[] = [
                    'tool_id' => $stage,
                    'params'  => ['post_id' => $postId],
                ];
            }

            // 执行（组内并行）
            $prevCtx = ToolRegistry::enterSystemContext();
            try {
                $stageResults = $parallel->execute($toolCalls, 0); // 0 = AI system 用户
            } finally {
                ToolRegistry::restoreSystemContext($prevCtx);
            }
            $allSuccess = true;
            foreach ($stageResults as $sr) {
                if (!$sr['success']) {
                    $allSuccess = false;
                    break;
                }
            }

            if ($allSuccess) {
                $meta['completed']++;
            } else {
                $meta['failed']++;
                $meta['errors'][] = [
                    'post_id' => $postId,
                    'post_title' => $post->post_title,
                    'stage_results' => $stageResults,
                ];
            }

            $meta['results'][] = [
                'post_id'       => $postId,
                'post_title'    => $post->post_title,
                'success'       => $allSuccess,
                'stage_results' => $stageResults,
            ];

            $meta['progress_pct'] = round(($meta['completed'] + $meta['failed']) / $meta['total'] * 100, 1);

            // 更新文章进度
            update_post_meta($postId, self::META_KEY, [
                'batch_id' => $batchId,
                'status'   => $allSuccess ? 'completed' : 'failed',
                'stages'   => $stageResults,
                'time'     => current_time('mysql'),
            ]);

            // 检查内存，防止 OOM
            if (memory_get_usage() > 40 * 1024 * 1024) {
                break;
            }
        }

        $meta['current_chunk']++;
        update_option(self::OPTION_QUEUE . '_' . $batchId, $stored, false);

        // 排队下一批
        if ($meta['current_chunk'] < count($chunks) && $meta['status'] === 'running') {
            $this->scheduleNext($batchId);
        } else {
            $this->markComplete($batchId, $stored);
        }
    }

    /**
     * 小批量即时执行
     */
    private function runFast(string $batchId): string
    {
        $this->processChunk($batchId);
        return $batchId;
    }

    /**
     * 调度下一批
     */
    private function scheduleNext(string $batchId): void
    {
        if (!wp_next_scheduled('xzd_ai_process_batch', [$batchId])) {
            wp_schedule_single_event(time() + 5, 'xzd_ai_process_batch', [$batchId]);
        }
    }

    /**
     * 标记完成
     */
    private function markComplete(string $batchId, array &$stored): void
    {
        $stored['meta']['status'] = 'completed';
        $stored['meta']['finished_at'] = current_time('mysql');
        $stored['meta']['progress_pct'] = 100;
        update_option(self::OPTION_QUEUE . '_' . $batchId, $stored, false);

        Logger::info('batch_complete', "批量任务 {$batchId} 完成", [
            'total'     => $stored['meta']['total'],
            'completed' => $stored['meta']['completed'],
            'failed'    => $stored['meta']['failed'],
        ]);
    }

    /**
     * 根据文章数量选择合适的批大小
     */
    public function detectBatchSize(int $total): int
    {
        if ($total <= 5) {
            return self::BATCH_SIZE_FAST;
        }
        if ($total <= 200) {
            return self::BATCH_SIZE_NORMAL;
        }
        return self::BATCH_SIZE_LARGE;
    }

    /**
     * 获取可用的流水线阶段
     */
    public function getAvailableStages(): array
    {
        return ToolRegistry::instance()->getPipelineStages('publish_post');
    }

    /**
     * 清理过期任务 (3天后)
     */
    public function cleanup(): int
    {
        global $wpdb;
        $prefix = self::OPTION_QUEUE . '_';

        $results = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
                $prefix . '%'
            )
        );

        $count = 0;
        $cutoff = time() - 3 * DAY_IN_SECONDS;

        foreach ($results as $row) {
            $option = get_option($row->option_name, null);
            if (!$option || !isset($option['meta']['finished_at'])) {
                continue;
            }
            $finished = strtotime($option['meta']['finished_at']);
            if ($finished && $finished < $cutoff) {
                delete_option($row->option_name);
                $count++;
            }
        }

        return $count;
    }
}
