import { Injectable, NotFoundException, OnModuleInit, ForbiddenException } from '@nestjs/common';
import { createLogger } from '@core/observability/logging/config/winston.config';
import type { AgentPlanMode, AgentPermissionMode } from '@prisma/client';
import type { AgentTool, ToolDescriptor, ToolInvocation, ToolResult } from './tool.types';
import {
  type HostCapability,
  defaultCapabilitiesForSurface,
} from './host-capability.types';
import { ProjectQueryTool } from './project-query.tool';
import { KnowledgeQueryTool } from './knowledge-query.tool';
import { WebSearchTool } from './web-search.tool';
import { WebFetchTool } from './web-fetch.tool';
import { TodoWriteTool } from './todo-write.tool';
import {
  TaskCreateTool,
  TaskUpdateTool,
  TaskListTool,
  TaskStopTool,
} from './task-tracker.tools';
import {
  CronCreateTool,
  CronListTool,
  CronUpdateTool,
  CronDeleteTool,
} from './cron.tools';
import { ApprovalSubmitTool } from './approval-submit.tool';
import {
  EnterPlanModeTool,
  ExitPlanModeTool,
  SetPermissionModeTool,
} from './mode-control.tools';
import { DelegateTaskTool } from './delegate-task.tool';
import { SendMessageTool } from './send-message.tool';
import { FileSaveTool } from './file-save.tool';
import { ScratchpadReadTool, ScratchpadWriteTool } from './scratchpad.tools';
import { AskUserTool } from './ask-user.tool';
import {
  FileReadTool,
  FileWriteTool,
  FileListTool,
  ClipboardReadTool,
  ClipboardWriteTool,
  NotifyPushTool,
  ShellOpenExternalTool,
  ShellExecTool,
} from './client-executor.tools';

const logger = createLogger('ToolRegistry');

export interface ListToolsContext {
  surface?: 'web' | 'desktop' | 'mobile' | 'teams' | 'cli';
  permissions?: string[];
  /** PR4.5: session 当前 Plan mode */
  planMode?: AgentPlanMode;
  /** PR4.5: session 当前 Permission mode */
  permissionMode?: AgentPermissionMode;
  /**
   * PR11.3：客户端上报的 HostBridge 能力集。
   * 未提供时按 surface 默认值兜底（详见 defaultCapabilitiesForSurface），
   * 真实动态协商在 PR 后续把"前端 capabilities() → session 存储 → 此处读取"链路打通。
   */
  capabilities?: readonly HostCapability[];
}

/**
 * Tool Registry（PR5 + PR4.5 mode 过滤）。
 *
 * - Surface / permission 过滤：descriptor.availability
 * - Plan mode REQUIRED：descriptor.writeAction=true 的工具不暴露（必须先 ExitPlanMode）
 * - Permission mode READ_ONLY：descriptor.writeAction=true 不暴露
 * - 控制工具（descriptor.controlTool=true）不受任何 mode 约束
 */
@Injectable()
export class ToolRegistry implements OnModuleInit {
  private readonly tools = new Map<string, AgentTool>();

  constructor(
    private readonly projectQuery: ProjectQueryTool,
    private readonly knowledgeQuery: KnowledgeQueryTool,
    private readonly webSearch: WebSearchTool,
    private readonly webFetch: WebFetchTool,
    private readonly todoWrite: TodoWriteTool,
    private readonly taskCreate: TaskCreateTool,
    private readonly taskUpdate: TaskUpdateTool,
    private readonly taskList: TaskListTool,
    private readonly taskStop: TaskStopTool,
    private readonly cronCreate: CronCreateTool,
    private readonly cronList: CronListTool,
    private readonly cronUpdate: CronUpdateTool,
    private readonly cronDelete: CronDeleteTool,
    private readonly approvalSubmit: ApprovalSubmitTool,
    private readonly enterPlan: EnterPlanModeTool,
    private readonly exitPlan: ExitPlanModeTool,
    private readonly setPermission: SetPermissionModeTool,
    private readonly delegateTask: DelegateTaskTool,
    private readonly sendMessage: SendMessageTool,
    private readonly fileSave: FileSaveTool,
    private readonly scratchpadRead: ScratchpadReadTool,
    private readonly scratchpadWrite: ScratchpadWriteTool,
    private readonly askUser: AskUserTool,
    private readonly fileRead: FileReadTool,
    private readonly fileWrite: FileWriteTool,
    private readonly fileList: FileListTool,
    private readonly clipboardRead: ClipboardReadTool,
    private readonly clipboardWrite: ClipboardWriteTool,
    private readonly notifyPush: NotifyPushTool,
    private readonly shellOpenExternal: ShellOpenExternalTool,
    private readonly shellExec: ShellExecTool,
  ) {}

  onModuleInit() {
    this.register(this.projectQuery);
    this.register(this.knowledgeQuery);
    this.register(this.webSearch);
    this.register(this.webFetch);
    this.register(this.todoWrite);
    this.register(this.taskCreate);
    this.register(this.taskUpdate);
    this.register(this.taskList);
    this.register(this.taskStop);
    this.register(this.cronCreate);
    this.register(this.cronList);
    this.register(this.cronUpdate);
    this.register(this.cronDelete);
    this.register(this.approvalSubmit);
    this.register(this.enterPlan);
    this.register(this.exitPlan);
    this.register(this.setPermission);
    this.register(this.delegateTask);
    this.register(this.sendMessage);
    this.register(this.fileSave);
    this.register(this.scratchpadRead);
    this.register(this.scratchpadWrite);
    this.register(this.askUser);
    // PR12 Client Executor 工具家族（fs / clipboard / notify）+ PR13 shell.exec 占位
    this.register(this.fileRead);
    this.register(this.fileWrite);
    this.register(this.fileList);
    this.register(this.clipboardRead);
    this.register(this.clipboardWrite);
    this.register(this.notifyPush);
    this.register(this.shellOpenExternal);
    this.register(this.shellExec);
    logger.log(`Registered ${this.tools.size} tools: ${[...this.tools.keys()].join(', ')}`);
  }

  register(tool: AgentTool): void {
    if (this.tools.has(tool.descriptor.name)) {
      throw new Error(`Tool already registered: ${tool.descriptor.name}`);
    }
    this.tools.set(tool.descriptor.name, tool);
  }

  /**
   * 删除所有 name 以 prefix 开头的工具，返回被删数量。
   * MCP server remove 时调用以同步清理 mcp:&lt;server&gt;:* 工具，避免 LLM
   * 仍看到已下线 server 的 tool descriptor 反复调用失败。
   */
  unregisterPrefix(prefix: string): number {
    let count = 0;
    for (const name of [...this.tools.keys()]) {
      if (name.startsWith(prefix)) {
        this.tools.delete(name);
        count += 1;
      }
    }
    return count;
  }

  list(ctx: ListToolsContext = {}): ToolDescriptor[] {
    // 热路径：每个 LLM turn 都跑（messages.service listAsProviderTools），
    // capability set 构造提到外层，避免 N tools × N 次 Set 分配。
    const haveCaps = new Set<HostCapability>(
      ctx.capabilities ?? defaultCapabilitiesForSurface(ctx.surface),
    );
    const descriptors: ToolDescriptor[] = [];
    for (const tool of this.tools.values()) {
      if (!this.isAvailable(tool.descriptor, ctx, haveCaps)) continue;
      descriptors.push(tool.descriptor);
    }
    return descriptors;
  }

  async invoke(name: string, invocation: ToolInvocation): Promise<ToolResult> {
    const tool = this.tools.get(name);
    if (!tool) throw new NotFoundException(`Tool not found: ${name}`);
    return tool.invoke(invocation);
  }

  assertAvailable(name: string, ctx: ListToolsContext): AgentTool {
    const tool = this.tools.get(name);
    if (!tool) throw new NotFoundException(`Tool not found: ${name}`);
    if (!this.isAvailable(tool.descriptor, ctx)) {
      throw new ForbiddenException(
        `Tool '${name}' not available — possibly blocked by surface/permission/mode (planMode=${ctx.planMode}, permissionMode=${ctx.permissionMode})`,
      );
    }
    return tool;
  }

  private isAvailable(
    d: ToolDescriptor,
    ctx: ListToolsContext,
    haveCaps?: ReadonlySet<HostCapability>,
  ): boolean {
    // 控制工具不受 mode 过滤
    if (d.controlTool) {
      // 仍走 surface/permission 基础过滤
      return this.basicAvailable(d, ctx, haveCaps);
    }

    // Plan mode REQUIRED：写动作工具被屏蔽
    if (ctx.planMode === 'REQUIRED' && d.writeAction) return false;

    // Permission mode READ_ONLY：写动作工具被屏蔽
    if (ctx.permissionMode === 'READ_ONLY' && d.writeAction) return false;

    return this.basicAvailable(d, ctx, haveCaps);
  }

  /**
   * PR4a：把 list() 结果转成 OpenAI tool 协议格式，喂给 ProviderRequest.tools。
   * 控制工具默认排除（它们要 sessionId 等内部参数，LLM 不该直接调）。
   */
  listAsProviderTools(ctx: ListToolsContext = {}): Array<{
    type: 'function';
    function: {
      name: string;
      description: string;
      parameters: {
        type: 'object';
        properties: Record<string, { type: string; description?: string }>;
        required?: string[];
      };
    };
  }> {
    return this.list(ctx)
      .filter((d) => !d.controlTool)
      .map((d) => {
        const properties: Record<string, { type: string; description?: string }> = {};
        const required: string[] = [];
        for (const [key, schema] of Object.entries(d.inputSchema)) {
          properties[key] = { type: schema.type, description: schema.description };
          if (schema.required) required.push(key);
        }
        return {
          type: 'function' as const,
          function: {
            name: d.name,
            description: d.description,
            parameters: {
              type: 'object' as const,
              properties,
              required: required.length > 0 ? required : undefined,
            },
          },
        };
      });
  }

  private basicAvailable(
    d: ToolDescriptor,
    ctx: ListToolsContext,
    haveCaps?: ReadonlySet<HostCapability>,
  ): boolean {
    if (d.availability?.surface && ctx.surface && !d.availability.surface.includes(ctx.surface)) {
      return false;
    }
    if (d.availability?.permissions && ctx.permissions) {
      const ok = d.availability.permissions.every((p) =>
        ctx.permissions!.some((have) => have === p || have === '*'),
      );
      if (!ok) return false;
    }
    // PR11.3：requiredCapabilities ⊆ 客户端能力集（缺一即不可见）
    if (d.availability?.requiredCapabilities && d.availability.requiredCapabilities.length > 0) {
      // haveCaps 由 list() 入口预构造；单 tool 入口（assertAvailable）走 fallback 构造
      const caps =
        haveCaps ??
        new Set<HostCapability>(
          ctx.capabilities ?? defaultCapabilitiesForSurface(ctx.surface),
        );
      const ok = d.availability.requiredCapabilities.every((cap) => caps.has(cap));
      if (!ok) return false;
    }
    return true;
  }
}
