import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { createLogger } from '@core/observability/logging/config/winston.config';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { ProviderRegistry } from '../providers/provider-registry.service';
import type {
  ModelRoutingRule,
  ModelRoutingScope,
  ModelRoutingMatchSource,
} from '@prisma/client';
import type {
  RoutingRequest,
  RoutingDecision,
  RoutingRulePattern,
  ProviderModelTuple,
} from './routing.types';
import { matchesPattern } from './pattern-matcher';

const logger = createLogger('ModelRouter');

/**
 * ModelRouter（G10 核心差异化，PR3.5）。
 *
 * 三策略（默认 A 优先 → C 兜底覆盖 → B 兜底）：
 *   A. Rule-based 静态规则（ORGANIZATION scope，priority 升序）
 *   B. LLM-routed 动态（**PR3.5 day-1 暂未实现**，跳过——所有未匹配 fallback 到默认 provider）
 *   C. Scope override（PROJECT > USER > ORGANIZATION，priority 升序）
 *
 * **scope override 优先级最高**：先查 PROJECT scope；命中即返回；
 * 然后 USER；最后 ORGANIZATION 规则按 priority 升序排第一个匹配；
 * 都不命中 → DEFAULT 决策（注入默认 mock provider）。
 *
 * 详见 docs/modules/agent/02-architecture.md §1.12.3。
 */
@Injectable()
export class ModelRouter {
  constructor(
    private readonly prisma: PrismaService,
    @Inject(forwardRef(() => ProviderRegistry))
    private readonly providerRegistry: ProviderRegistry,
  ) {}

  async decide(request: RoutingRequest): Promise<RoutingDecision> {
    // 一次性并发拉所有三层 scope 的规则（同 org，3 个 findMany），按优先级在内存里匹配
    const filters: Array<{
      scope: ModelRoutingScope;
      refId: string;
      matchSource: 'SCOPE_OVERRIDE' | 'RULE';
    }> = [];
    if (request.projectId) {
      filters.push({ scope: 'PROJECT', refId: request.projectId, matchSource: 'SCOPE_OVERRIDE' });
    }
    filters.push({ scope: 'USER', refId: request.userId, matchSource: 'SCOPE_OVERRIDE' });
    filters.push({
      scope: 'ORGANIZATION',
      refId: request.organizationId,
      matchSource: 'RULE',
    });

    const ruleSets = await Promise.all(
      filters.map((f) =>
        this.prisma.modelRoutingRule.findMany({
          where: {
            organizationId: request.organizationId,
            scope: f.scope,
            scopeRefId: f.refId,
            enabled: true,
          },
          orderBy: { priority: 'asc' },
        }),
      ),
    );

    // 按 filters 顺序遍历（PROJECT → USER → ORGANIZATION）
    for (let i = 0; i < filters.length; i++) {
      const f = filters[i];
      const matched = this.matchFirstInList(request, ruleSets[i]);
      if (matched) {
        return toDecision(
          matched,
          f.matchSource,
          matched.reasoning ?? `${f.scope.toLowerCase()} rule matched`,
        );
      }
    }

    // B. LLM-routed —— 用 qwen-turbo 分析任务推荐主模型（PR3.5 strategy B）
    //    仅在 Qwen 可用 + provider 实际能调时启用；失败 fallback 到 default
    try {
      const llmDecision = await this.classifyWithLLM(request);
      if (llmDecision) return llmDecision;
    } catch (err) {
      logger.warn(`LLM-routed strategy B failed; falling through: ${(err as Error).message}`);
    }

    // DEFAULT：优先国内 Qwen（PR3 plan B），mock 仅在 Qwen 不可用时兜底
    return {
      primary: { provider: 'qwen', model: 'qwen-plus' },
      fallbacks: [
        { provider: 'qwen', model: 'qwen-turbo' },
        { provider: 'mock', model: 'mock-fast' },
      ],
      matchSource: 'default',
      reasoning:
        'no matching rule; default routing to qwen-plus (国内 LLM, 不涉数据出境); mock 兜底',
    };
  }

  /**
   * 持久化决策到 ModelRoutingDecision 表。
   * 即便没成功调 provider 也写——便于排查"哪些请求 router 决策给了 X 但实际没跑出来"。
   */
  async persistDecision(args: {
    organizationId: string;
    sessionId?: string;
    turnId?: string;
    request: RoutingRequest;
    decision: RoutingDecision;
  }) {
    return this.prisma.modelRoutingDecision.create({
      data: {
        organizationId: args.organizationId,
        sessionId: args.sessionId ?? null,
        turnId: args.turnId ?? null,
        request: args.request as never,
        decision: args.decision as never,
        matchSource: matchSourceToEnum(args.decision.matchSource),
        matchedRuleId: args.decision.matchedRuleId ?? null,
        primaryProvider: args.decision.primary.provider,
        primaryModel: args.decision.primary.model,
        estimatedCostUsd: args.decision.estimatedCostUsd ?? null,
      },
    });
  }

  /**
   * 决策跑完后回填实际 cost / latency（供学习反馈用）。
   */
  async recordOutcome(args: {
    decisionId: string;
    actualCostUsd?: number;
    actualLatencyMs?: number;
  }) {
    return this.prisma.modelRoutingDecision.update({
      where: { id: args.decisionId },
      data: {
        actualCostUsd: args.actualCostUsd ?? null,
        actualLatencyMs: args.actualLatencyMs ?? null,
      },
    });
  }

  // ----- private -----

  /**
   * PR3.5 strategy B：用 qwen-turbo 当任务分类器，输出推荐主 model。
   *
   * 输入：RoutingRequest（含 task signals）+ 候选 model 池
   * 输出：RoutingDecision（matchSource='llm_routed' + reasoning）
   *
   * 失败 / 不可用 → 返回 null 让上层走 DEFAULT 兜底。
   */
  private async classifyWithLLM(req: RoutingRequest): Promise<RoutingDecision | null> {
    const candidates = ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-max-longcontext'];
    const systemPrompt = `你是 FF AI Workspace ModelRouter 的任务分类器。
给定一次 agent 请求的 5 信号，从候选模型池选一个最合适的。规则：
- 简单聊天 / 短回复 → qwen-turbo（便宜快）
- 中等复杂的代码 / 翻译 / 中等推理 → qwen-plus
- 复杂推理 / 多步思考 / 高质量代码 → qwen-max
- 上下文 > 30000 tokens → qwen-max-longcontext

只回一个 JSON：{"model": "<选中的 model>", "reason": "<一句中文理由>"}。不要任何其他文字。`;
    const userPrompt = JSON.stringify({
      taskType: req.taskType,
      toolName: req.toolName,
      contextTokens: req.contextTokens,
      hasImage: req.hasImage,
      turnDepth: req.turnDepth,
      latencyPriority: req.latencyPriority,
      costPriority: req.costPriority,
    });
    let response;
    try {
      response = await this.providerRegistry.invoke(
        {
          model: 'qwen-turbo',
          messages: [
            { role: 'system', content: systemPrompt },
            { role: 'user', content: userPrompt },
          ],
          maxTokens: 200,
          temperature: 0.1,
        },
        { model: 'qwen-turbo' },
      );
    } catch (err) {
      logger.warn(`LLM-routed invoke failed: ${(err as Error).message}`);
      return null;
    }

    // 提取 JSON（LLM 可能带 ```json 围栏）
    const m = response.text.match(/\{[\s\S]*\}/);
    if (!m) return null;
    let parsed: { model?: string; reason?: string };
    try {
      parsed = JSON.parse(m[0]);
    } catch {
      return null;
    }
    if (!parsed.model || !candidates.includes(parsed.model)) return null;

    logger.log(`LLM-routed → ${parsed.model} (reason: ${parsed.reason ?? '?'})`);
    return {
      primary: { provider: 'qwen', model: parsed.model },
      fallbacks: [{ provider: 'qwen', model: 'qwen-plus' }],
      matchSource: 'llm_routed',
      reasoning: parsed.reason ?? 'LLM classifier decision',
    };
  }

  private matchFirstInList(
    request: RoutingRequest,
    rules: ModelRoutingRule[],
  ): ModelRoutingRule | null {
    for (const rule of rules) {
      const pattern = rule.pattern as unknown as RoutingRulePattern;
      if (matchesPattern(request, pattern)) {
        logger.log(`Matched rule '${rule.name}' (priority ${rule.priority}, scope ${rule.scope})`);
        return rule;
      }
    }
    return null;
  }
}

function toDecision(
  rule: ModelRoutingRule,
  matchSource: 'RULE' | 'SCOPE_OVERRIDE',
  reasoning: string,
): RoutingDecision {
  return {
    primary: rule.primary as unknown as ProviderModelTuple,
    fallbacks: (rule.fallbacks as unknown as ProviderModelTuple[]) ?? [],
    matchSource: matchSource === 'RULE' ? 'rule' : 'scope_override',
    matchedRuleId: rule.id,
    reasoning,
  };
}

function matchSourceToEnum(s: RoutingDecision['matchSource']): ModelRoutingMatchSource {
  switch (s) {
    case 'rule':
      return 'RULE';
    case 'llm_routed':
      return 'LLM_ROUTED';
    case 'scope_override':
      return 'SCOPE_OVERRIDE';
    case 'default':
      return 'DEFAULT';
  }
}
