import { Init, Logger, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { ILogger } from '@midwayjs/logger';
import { Repository, In, Like } from 'typeorm';
import { NetaClawToolEntity } from '../entity/tool.js';
import { getAllToolSchemas, getToolSchema } from '../tools/catalog.js';
import { buildToolRuntimeProfile, type ToolGovernanceExtra } from '../tools/manifest.js';
import { buildToolFileCapabilityProjection, buildToolRendererCapabilityProjection } from '../tools/presentation.js';

@Provide()
@Scope(ScopeEnum.Singleton)
export class NetaClawToolRegistryService {
  @Logger()
  logger: ILogger;

  @InjectEntityModel(NetaClawToolEntity)
  toolRepo: Repository<NetaClawToolEntity>;

  @Init()
  async init() {
    try {
      await this.syncCatalogToDb();
    } catch (error: any) {
      this.logger.error('[ToolRegistry] sync catalog on init failed: %s', error?.message || error);
    }
  }

  private createDefaults(schema: NonNullable<ReturnType<typeof getToolSchema>>) {
    const s = schema;
    const isCore = s.isCore ? 1 : 0;
    const canDisable = typeof s.canDisable === 'boolean'
      ? (s.canDisable ? 1 : 0)
      : (s.isCore ? 0 : 1);
    const supportsPromptHint = s.supportsPromptHint ? 1 : 0;
    return {
      name: s.name,
      label: s.name,
      toolset: s.toolset,
      description: s.description,
      visibility: s.visibility ?? 'tool',
      capability: s.capability ?? 'text',
      status: 1,
      isCore,
      canDisable,
      supportsPromptHint,
      requiresModel: s.requiresModel ? 1 : 0,
      promptHint: null,
      sort: 0,
      extra: null,
    };
  }

  private buildRuntimeProfile(name: string) {
    return this.buildRuntimeProfileWithExtra(name, null);
  }

  private buildRuntimeProfileWithExtra(name: string, extra: ToolGovernanceExtra | null | undefined) {
    const profile = buildToolRuntimeProfile(name, extra);
    return {
      ...profile,
      requiresShell: profile.requiresShell === true,
      requiresWrite: profile.requiresWrite === true,
      requiresSkillScope: profile.requiresSkillScope === true,
    };
  }

  private parseExtra(extra: unknown): ToolGovernanceExtra | null {
    if (!extra || typeof extra !== 'object' || Array.isArray(extra)) {
      return null;
    }
    return extra as ToolGovernanceExtra;
  }

  private enrichTool<T extends NetaClawToolEntity | Partial<NetaClawToolEntity>>(tool: T) {
    const extra = this.parseExtra(tool.extra);
    return {
      ...tool,
      governancePolicy: {
        allowInSubagent: extra?.allowInSubagent !== false,
        workerRoutingStrategy: extra?.workerRoutingStrategy ?? 'auto',
      },
      runtimeProfile: this.buildRuntimeProfileWithExtra(String(tool.name || ''), extra),
      presentation: {
        fileCapability: buildToolFileCapabilityProjection(String(tool.name || '')),
        rendererCapability: buildToolRendererCapabilityProjection(String(tool.name || '')),
      },
    };
  }

  private async ensureCatalogSeeded() {
    const total = await this.toolRepo.count();
    if (total === 0 && getAllToolSchemas().length > 0) {
      await this.syncCatalogToDb();
    }
  }

  async syncCatalogToDb() {
    const schemas = getAllToolSchemas();
    const names = schemas.map(item => item.name);
    const existing = names.length > 0 ? await this.toolRepo.find({ where: { name: In(names) } }) : [];
    const existingMap = new Map(existing.map(item => [item.name, item]));

    const saves: Partial<NetaClawToolEntity>[] = [];
    for (const schema of schemas) {
      const current = existingMap.get(schema.name);
      const defaults = this.createDefaults(schema);
      if (!current) {
        saves.push(this.toolRepo.create(defaults));
        continue;
      }
      saves.push({
        ...current,
        toolset: defaults.toolset,
        visibility: defaults.visibility,
        capability: defaults.capability,
        isCore: typeof current.isCore === 'number' ? current.isCore : defaults.isCore,
        canDisable: typeof current.canDisable === 'number' ? current.canDisable : defaults.canDisable,
        supportsPromptHint: defaults.supportsPromptHint,
        requiresModel: defaults.requiresModel === 1 ? 1 : (typeof current.requiresModel === 'number' ? current.requiresModel : defaults.requiresModel),
        label: current.label ?? defaults.label,
        description: current.description ?? defaults.description,
      });
    }

    if (saves.length > 0) {
      await this.toolRepo.save(saves);
    }

    return { total: schemas.length };
  }

  async page(params: {
    page?: number;
    size?: number;
    keyWord?: string;
    toolset?: string;
    capability?: string;
    status?: number;
    requiresModel?: number;
  }) {
    await this.ensureCatalogSeeded();
    const { page = 1, size = 20, keyWord, toolset, capability, status, requiresModel } = params;
    const where: Record<string, any> = {};
    if (keyWord) where.name = Like(`%${keyWord}%`);
    if (toolset) where.toolset = toolset;
    if (capability) where.capability = capability;
    if (typeof status === 'number') where.status = status;
    if (typeof requiresModel === 'number') where.requiresModel = requiresModel;
    const [list, total] = await this.toolRepo.findAndCount({
      where,
      order: { sort: 'ASC', createTime: 'DESC' },
      skip: (page - 1) * size,
      take: size,
    });
    return {
      list: list.map(item => this.enrichTool(item)),
      pagination: { page, size, total },
    };
  }

  async info(id: number) {
    const item = await this.toolRepo.findOneBy({ id });
    return item ? this.enrichTool(item) : null;
  }

  async update(data: Partial<NetaClawToolEntity>) {
    const current = await this.toolRepo.findOneBy({ id: data.id });
    if (!current) throw new Error('Tool 不存在');

    const schema = getToolSchema(current.name);
    if (!schema) throw new Error(`Tool "${current.name}" 未在 catalog 中注册`);

    const nextIsCore = typeof data.isCore === 'number' ? data.isCore : current.isCore;
    const nextCanDisable = typeof data.canDisable === 'number' ? data.canDisable : current.canDisable;
    const nextStatus = typeof data.status === 'number' ? data.status : current.status;

    if (nextIsCore === 1 && nextCanDisable === 0 && nextStatus === 0) {
      throw new Error('核心工具不允许禁用');
    }

    if (!schema.supportsPromptHint && typeof data.promptHint === 'string' && data.promptHint.trim()) {
      throw new Error('该工具不支持配置 Prompt Hint');
    }

    const nextExtra = this.parseExtra(data.extra ?? current.extra) ?? {};
    if (
      nextExtra.workerRoutingStrategy
      && !['auto', 'force-local', 'force-main-process-proxy', 'force-disabled'].includes(nextExtra.workerRoutingStrategy)
    ) {
      throw new Error('无效的 Worker 路由治理策略');
    }

    await this.toolRepo.save({
      ...current,
      label: data.label ?? current.label,
      description: data.description ?? current.description,
      status: nextStatus,
      isCore: nextIsCore,
      canDisable: nextCanDisable,
      promptHint: schema.supportsPromptHint ? (data.promptHint ?? current.promptHint) : null,
      sort: typeof data.sort === 'number' ? data.sort : current.sort,
      modelChannelId: data.modelChannelId ?? current.modelChannelId,
      modelId: data.modelId ?? current.modelId,
      extra: nextExtra,
    });
  }

  async getToolByName(name: string): Promise<NetaClawToolEntity | null> {
    return this.toolRepo.findOneBy({ name });
  }

  async getToolModelConfig(toolName: string): Promise<{
    modelChannelId: number; modelId: string; promptHint: string | null;
  } | null> {
    const tool = await this.toolRepo.findOneBy({ name: toolName });
    if (!tool?.modelChannelId || !tool?.modelId) return null;
    return { modelChannelId: tool.modelChannelId, modelId: tool.modelId, promptHint: tool.promptHint };
  }

  async all() {
    await this.ensureCatalogSeeded();
    const list = await this.toolRepo.find({ order: { sort: 'ASC', createTime: 'DESC' } });
    return list.map(item => this.enrichTool(item));
  }

  async listForAgentConfig() {
    await this.ensureCatalogSeeded();
    const list = await this.toolRepo.find({
      order: { toolset: 'ASC', sort: 'ASC', createTime: 'DESC' },
    });
    const toolsets = [...new Set(list.map(item => item.toolset))];
    return { toolsets, tools: list.map(item => this.enrichTool(item)) };
  }
}
