import { Provide, Scope, ScopeEnum, Inject, Logger } from '@midwayjs/core';
import type { ILogger } from '@midwayjs/logger';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import type { DesktopTask } from '../runtime/types.js';
import { DesktopOpRuntime } from '../runtime/runtime.js';
import { DesktopMutex } from '../runtime/desktop_mutex.js';
import { AdapterRegistry } from '../runtime/adapters/registry.js';
import { DesktopOpActionLogEntity } from '../entity/desktop_op_action_log.js';

/** runAndWait 成功返回值 */
export interface RunAndWaitResult {
  ok: true;
  taskId: string;
  modelCalls: number;
  steps: number;
  durationMs: number;
}

/** 等待中的 Promise 控制柄 */
interface PendingWaiter {
  resolve: (r: RunAndWaitResult) => void;
  reject: (e: Error) => void;
  timer: NodeJS.Timeout;
}

/** 每个 queueKey 队列最大长度，超出则丢弃最早的任务 */
const MAX_QUEUE_PER_KEY = 20;

/**
 * Desktop Op 服务层 — per-key 队列 + 全局键鼠互斥 + 同步等待 API
 *
 * - runAndWait(task, timeoutMs)：tool execute 入口，同步等结果
 * - enqueue(task)：fire-and-forget（Layer 2 留口）
 * - abortByFilter(filter, reason)：cascade 取消（channel.delete / disable 触发）
 *
 * 终态（success / 各错误 token / aborted / queue-overflow）均写一行 desktop_op_action_log。
 */
@Provide()
@Scope(ScopeEnum.Singleton)
export class DesktopOpService {
  @Inject() runtime!: DesktopOpRuntime;
  @Inject() desktopMutex!: DesktopMutex;
  @Inject() adapterRegistry!: AdapterRegistry;
  @InjectEntityModel(DesktopOpActionLogEntity)
  logRepo!: Repository<DesktopOpActionLogEntity>;
  @Logger() logger!: ILogger;

  /** per-key 任务队列 */
  private readonly queues = new Map<string, DesktopTask[]>();
  /** per-key worker Promise（运行中则存在，结束后删除） */
  private readonly workers = new Map<string, Promise<void>>();
  /** 正在执行的任务对应的 AbortController，key = task.id */
  private readonly aborters = new Map<string, AbortController>();
  /** 等待 runAndWait 结果的 Promise 控制柄，key = task.id */
  private readonly waiters = new Map<string, PendingWaiter>();
  /** task.id → queueKey 反查，用于 abortByFilter 后清理 */
  private readonly taskKey = new Map<string, string>();
  /** 正在执行的任务对象本体，key = task.id；用于 abortByFilter 第二阶段精确过滤 */
  private readonly runningTasks = new Map<string, DesktopTask>();

  /**
   * 将任务加入 per-key 队列，立即返回位置信息。
   * 若队列已满（>= MAX_QUEUE_PER_KEY），溢出最旧的一个任务并写日志。
   */
  enqueue(task: DesktopTask): { taskId: string; queuePosition: number } {
    const key = this.queueKey(task);
    let q = this.queues.get(key);
    if (!q) {
      q = [];
      this.queues.set(key, q);
    }

    // 队列已满：丢弃最旧的
    if (q.length >= MAX_QUEUE_PER_KEY) {
      const dropped = q.shift()!;
      this.persistLog(dropped, { status: 'queue-overflow' });
      this.rejectWaiter(dropped.id, new Error('queue-overflow'));
      this.taskKey.delete(dropped.id);
    }

    q.push(task);
    // 在 ensureWorker 前记录位置：worker 同步消费时队列会被 shift，需要先保存
    const queuePosition = q.length;
    this.taskKey.set(task.id, key);
    this.ensureWorker(key);
    return { taskId: task.id, queuePosition };
  }

  /**
   * 提交任务并同步等待结果，超时则 reject 并触发 abort。
   * @param task      待执行的任务
   * @param timeoutMs 超时毫秒数（默认 60s）
   */
  async runAndWait(task: DesktopTask, timeoutMs = 60000): Promise<RunAndWaitResult> {
    return new Promise<RunAndWaitResult>((resolve, reject) => {
      const timer = setTimeout(() => {
        this.waiters.delete(task.id);
        // 超时时中断该任务（若正在运行）并清理队列中的它
        this.abortByFilter(t => t.id === task.id, 'task-timeout');
        reject(new Error('task-timeout'));
      }, timeoutMs);

      this.waiters.set(task.id, { resolve, reject, timer });
      this.enqueue(task);
    });
  }

  /**
   * 按过滤条件批量取消任务（cascade abort）。
   * - pending 队列中的任务：直接移除并写 aborted 日志
   * - 正在执行的任务：调用其 AbortController.abort()
   *
   * @param filter 返回 true 则取消该任务
   * @param reason 取消原因，写入 abortedReason 字段
   */
  abortByFilter(filter: (task: DesktopTask) => boolean, reason: string): void {
    // 第一步：清理 pending 队列
    for (const [k, q] of this.queues.entries()) {
      const keep: DesktopTask[] = [];
      for (const t of q) {
        if (filter(t)) {
          this.persistLog(t, { status: 'aborted', abortedReason: reason });
          this.rejectWaiter(t.id, new Error(reason));
          this.taskKey.delete(t.id);
        } else {
          keep.push(t);
        }
      }
      this.queues.set(k, keep);
    }

    // 第二步：精确中断匹配 filter 的正在运行任务（v4 修复:不再误杀其他 channel 的运行中任务）
    for (const [taskId, ac] of this.aborters.entries()) {
      const runningTask = this.runningTasks.get(taskId);
      if (runningTask && filter(runningTask)) {
        ac.abort(reason);
      }
    }
  }

  // ──────────────────────────────────────────────────────────────────
  // 私有：Worker 管理
  // ──────────────────────────────────────────────────────────────────

  /** 确保指定 key 有一个 worker 在跑，没有则启动 */
  private ensureWorker(key: string): void {
    if (this.workers.has(key)) return;
    const promise = this.workerLoop(key).finally(() => this.workers.delete(key));
    this.workers.set(key, promise);
  }

  /**
   * per-key worker 主循环：从队列取任务 → 获取 Mutex → 执行 → 写日志 → 循环
   */
  private async workerLoop(key: string): Promise<void> {
    for (;;) {
      const q = this.queues.get(key);
      if (!q || q.length === 0) return;

      const task = q.shift()!;
      this.taskKey.delete(task.id);

      const abort = new AbortController();
      this.aborters.set(task.id, abort);
      this.runningTasks.set(task.id, task);
      const startedAt = Date.now();
      let release: (() => void) | null = null;

      try {
        // 获取全局键鼠互斥锁
        release = await this.desktopMutex.acquire(task.id, task.appId);
        // 执行任务
        const result = await this.runtime.runTask(task, abort.signal);
        // 成功：写日志 + 通知 waiter
        this.persistLog(task, {
          status: 'success',
          modelCalls: result.modelCalls,
          steps: result.steps,
          durationMs: result.durationMs,
        });
        this.resolveWaiter(task.id, {
          ok: true,
          taskId: task.id,
          modelCalls: result.modelCalls,
          steps: result.steps,
          durationMs: result.durationMs,
        });
      } catch (err: any) {
        const message = String(err?.message ?? err);
        const status = this.errToStatus(message);
        this.persistLog(task, {
          status,
          error: message,
          abortedReason: abort.signal.aborted ? this.extractAbortReason(abort.signal) : undefined,
          durationMs: Date.now() - startedAt,
        });
        this.rejectWaiter(task.id, err instanceof Error ? err : new Error(message));
      } finally {
        if (release) release();
        this.aborters.delete(task.id);
        this.runningTasks.delete(task.id);
      }
    }
  }

  // ──────────────────────────────────────────────────────────────────
  // 私有：工具方法
  // ──────────────────────────────────────────────────────────────────

  /** 计算任务的 queueKey = appId:target-key */
  private queueKey(task: DesktopTask): string {
    try {
      const adapter = this.adapterRegistry.get(task.appId);
      return task.appId + ':' + adapter.queueKey(task.target);
    } catch {
      // adapter 未注册 — 用 fallback，让 worker 内 runtime 抛 app-not-allowed
      return task.appId + ':__unknown__';
    }
  }

  /**
   * 将错误消息映射为已知 status token。
   * 优先匹配已知前缀，否则返回 'error'。
   */
  private errToStatus(msg: string): string {
    const tokens = [
      'window-not-found',
      'precondition-failed',
      'app-not-allowed',
      'dangerous-key-blocked',
      'dangerous-action-blocked',
      'model-failed',
      'model-hallucinated',
      'verify-failed',
      'queue-overflow',
      'aborted',
      'task-timeout',
      'unsupported-platform',
      'model-channel-not-configured',
      'model-channel-not-found',
      'model-channel-no-models',
      'invalid-task',
    ];
    for (const t of tokens) {
      if (msg.startsWith(t)) return t;
    }
    return 'error';
  }

  /** 从 AbortSignal 中提取 abort 原因字符串 */
  private extractAbortReason(sig: AbortSignal): string {
    const r = (sig as any).reason;
    if (typeof r === 'string') return r;
    if (r?.message) return r.message;
    return 'aborted';
  }

  /** 通知 runAndWait 成功 */
  private resolveWaiter(taskId: string, result: RunAndWaitResult): void {
    const w = this.waiters.get(taskId);
    if (!w) return;
    clearTimeout(w.timer);
    this.waiters.delete(taskId);
    w.resolve(result);
  }

  /** 通知 runAndWait 失败 */
  private rejectWaiter(taskId: string, err: Error): void {
    const w = this.waiters.get(taskId);
    if (!w) return;
    clearTimeout(w.timer);
    this.waiters.delete(taskId);
    w.reject(err);
  }

  /**
   * 写操作日志（fire-and-forget）。
   * 失败仅打 warn 日志，不影响主流程。
   */
  private persistLog(task: DesktopTask, extra: Partial<DesktopOpActionLogEntity>): void {
    void this.logRepo
      .save({
        taskId: task.id,
        appId: task.appId,
        targetJson: task.target ?? null,
        actionType: task.actionType,
        paramsPreview: this.previewParams(task.params),
        finalText: this.extractFinalText(task),
        channelId: task.target?.channelId ?? null,
        roomName: task.target?.roomName ?? task.target?.conversation ?? null,
        modelChannelId: task.modelChannelId ?? null,
        modelCalls: 0,
        steps: 0,
        durationMs: 0,
        status: 'error',
        error: null,
        abortedReason: null,
        ...extra,
      } as any)
      .catch((e: any) =>
        this.logger?.warn?.('[desktop-op] persistLog 写库失败: %s', e?.message ?? e)
      );
  }

  /** 参数预览，截断至 200 字符 */
  private previewParams(p: any): string {
    if (!p) return '';
    try {
      const s = JSON.stringify(p);
      return s.length > 200 ? s.slice(0, 200) : s;
    } catch {
      return '';
    }
  }

  /** 从任务参数中提取最终文本 */
  private extractFinalText(task: DesktopTask): string | null {
    return task.params?.text ?? task.params?.finalText ?? null;
  }
}
