/**
 * AgentToolBootstrap — 启动期扫描 forFeature 登记的业务 service,
 * 把 @AgentTool 装饰的方法适配为 AgentTool 接口并注册到 ToolRegistry。
 *
 * 设计 (#410 plan-review Q1.1 = C + Q2.1 = B):
 *   1. 不用 DiscoveryService 全局扫 — 仅遍历 AgentToolsCollector.list() 的 service class
 *   2. 通过 ModuleRef.get(class) 拿 NestJS DI 解析的实例 (复用 service 依赖图)
 *   3. 用 Reflect 读 @AgentTool 元数据 (AGENT_TOOL_METADATA / AGENT_TOOL_METHODS)
 *   4. 适配方法签名 method(input, ctx) → 老 AgentTool { descriptor, invoke }
 *
 * **v1.0 PR-A 当前 commit 范围**: 仅做"扫描 + 注册",**不含包装层**
 *   (audit / quota / destructive mode-aware / 异常三档 / sanitize / async queue
 *    详 standards/21 §1.2 + §1.3 + §3.7 + §3.10);异常处理 v1.0 PoC 用 try/catch
 *    + ToolResult.ok=false 简单兜底,生产级三档处理留 PR-A 后续 commit。
 *
 * **业务方法签名约定** (详 standards/21 §3.2):
 *   async <name>ForAgent(input: <DTO>, ctx: AgentContext): Promise<<ResultDTO>>
 *
 * 命名映射 (legacy ToolInvocation → AgentContext):
 *   - ToolInvocation.{ userId, organizationId, sessionId, turnId } → AgentContext 同名字段
 *   - surface: 默认 'web' (ToolInvocation 当前不带 surface;后续 PR 扩 ToolInvocation 字段)
 *   - trace: { traceId: '' } 占位 (后续 PR 接 OTel)
 */

import { Injectable, OnApplicationBootstrap, type Type } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { createLogger } from '@core/observability/logging/config/winston.config';
import {
  getAgentToolMethods,
  getAgentToolMetadata,
} from './agent-tool.decorator';
import { AgentToolsCollector } from './agent-tools-collector.service';
import { ToolRegistry } from '../tools/tool-registry.service';
import type { AgentContext } from './types';
import type {
  AgentTool as LegacyAgentTool,
  ToolInvocation as LegacyToolInvocation,
  ToolResult as LegacyToolResult,
} from '../tools/tool.types';

const logger = createLogger('AgentToolBootstrap');

@Injectable()
export class AgentToolBootstrap implements OnApplicationBootstrap {
  constructor(
    private readonly collector: AgentToolsCollector,
    private readonly moduleRef: ModuleRef,
    private readonly toolRegistry: ToolRegistry,
  ) {}

  async onApplicationBootstrap(): Promise<void> {
    const serviceClasses = this.collector.list();
    if (serviceClasses.length === 0) {
      logger.log('no forFeature service registered; skip scan');
      return;
    }

    let registered = 0;
    for (const cls of serviceClasses) {
      const instance = this.resolveInstance(cls);
      if (!instance) {
        logger.warn(
          `cannot resolve ${cls.name} via ModuleRef; ` +
            `ensure the class is provided in your NestJS module's providers[]`,
        );
        continue;
      }

      const methods = getAgentToolMethods(cls);
      if (methods.length === 0) {
        logger.warn(
          `${cls.name} forFeature 登记但无 @AgentTool 方法 — 是否漏标装饰器?`,
        );
        continue;
      }

      const prototype = Object.getPrototypeOf(instance);
      for (const methodKey of methods) {
        const metadata = getAgentToolMetadata(prototype, methodKey);
        if (!metadata) {
          logger.warn(
            `${cls.name}.${String(methodKey)} 在 method 清单中,但 metadata 读取为空 — 装饰器异常?`,
          );
          continue;
        }
        try {
          this.toolRegistry.register(
            this.adapt(instance, methodKey, metadata, cls.name),
          );
          registered += 1;
        } catch (err) {
          logger.error(
            `failed to register ${cls.name}.${String(methodKey)} → ${metadata.name}: ${
              (err as Error).message
            }`,
          );
          throw err; // fail-fast: 重名 / 注册异常 应该让启动失败
        }
      }
    }

    logger.log(
      `scan complete: ${registered} @AgentTool method(s) from ${serviceClasses.length} service class(es) registered`,
    );
  }

  private resolveInstance(cls: Type): unknown {
    try {
      // strict: false 允许跨 module 解析;若 service 没被 NestJS provide 会返回 undefined
      return this.moduleRef.get(cls, { strict: false });
    } catch {
      return undefined;
    }
  }

  /**
   * 把业务方法 (input, ctx) 签名适配为旧 AgentTool { descriptor, invoke(ToolInvocation) }。
   *
   * v1.0 PR-A 当前 commit: 简单 try/catch + ok=false (无包装层);
   * PR-A 后续 commit 加 audit/quota/destructive/异常三档/sanitize。
   */
  private adapt(
    instance: object,
    methodKey: string | symbol,
    metadata: import('./types').AgentToolMetadata,
    serviceName: string,
  ): LegacyAgentTool {
    const methodName = String(methodKey);
    const method = (instance as Record<string, unknown>)[methodName];
    if (typeof method !== 'function') {
      throw new Error(
        `${serviceName}.${methodName} 标注 @AgentTool 但不是方法(类型: ${typeof method})`,
      );
    }

    return {
      descriptor: metadata,
      invoke: async (invocation: LegacyToolInvocation): Promise<LegacyToolResult> => {
        const ctx: AgentContext = {
          userId: invocation.userId,
          organizationId: invocation.organizationId,
          sessionId: invocation.sessionId ?? '',
          turnId: invocation.turnId ?? '',
          surface: 'web', // TODO PR-A 后续: ToolInvocation 加 surface 字段后透传
          trace: { traceId: '' }, // TODO PR-A 后续: 接 OTel
        };

        try {
          const output = await (method as (...args: unknown[]) => Promise<unknown>).call(
            instance,
            invocation.input,
            ctx,
          );
          return { ok: true, output };
        } catch (err) {
          // TODO PR-A 后续 commit: 异常三档 (业务校验/安全/系统) + sanitize 黑名单
          // 现 v1.0 PoC: 通用 fallback,避免业务方法异常炸 ToolRegistry.invoke 调用栈
          const message = err instanceof Error ? err.message : String(err);
          logger.error(
            `${serviceName}.${methodName} (tool=${metadata.name}) invoke failed: ${message}`,
          );
          return { ok: false, errorMessage: message };
        }
      },
    };
  }
}
