import { Injectable, OnModuleInit, ServiceUnavailableException, NotFoundException } from '@nestjs/common';
import { createLogger } from '@core/observability/logging/config/winston.config';
import type {
  ModelProvider,
  ProviderRequest,
  ProviderResponse,
  ProviderStreamChunk,
} from './provider.types';
import { MockProvider } from './mock.provider';
import { AnthropicProvider } from './anthropic.provider';
import { OpenAIProvider } from './openai.provider';
import { QwenProvider } from './qwen.provider';

const logger = createLogger('ProviderRegistry');

export interface ResolveOptions {
  /**
   * 显式指定 model alias（如 'claude-opus-4-7' 或 'mock-fast'）。
   * 未指定 → 走 default fallback（mock-fast）；PR3.5 ModelRouter 进来后替换为路由决策。
   */
  readonly model?: string;
}

/**
 * Provider 注册表 + Resolver。
 *
 * PR3 范围：
 *  - 注册 3 个 provider（mock / anthropic / openai）
 *  - Resolver 按 model alias → provider 反查；
 *    法务合规未通过前 anthropic/openai isAvailable() 全 false，所有请求兜底到 mock
 *  - PR3.5 ModelRouter 会在 invokeWithRouting() 里加 5 信号决策层
 */
@Injectable()
export class ProviderRegistry implements OnModuleInit {
  private readonly providers = new Map<string, ModelProvider>();

  constructor(
    private readonly qwenProvider: QwenProvider,
    private readonly mockProvider: MockProvider,
    private readonly anthropicProvider: AnthropicProvider,
    private readonly openaiProvider: OpenAIProvider,
  ) {}

  onModuleInit() {
    // 注册顺序 = 解析优先级；Qwen 可用时优先（国内合规已通）
    this.register(this.qwenProvider);
    this.register(this.mockProvider);
    this.register(this.anthropicProvider);
    this.register(this.openaiProvider);
    logger.log(
      `Registered ${this.providers.size} providers: ${[...this.providers.keys()].join(', ')}`,
    );
    const available = this.listAvailable().map((p) => p.name);
    logger.log(`Available providers (PR3 day-1): ${available.join(', ') || '(none)'}`);
  }

  register(provider: ModelProvider): void {
    if (this.providers.has(provider.name)) {
      throw new Error(`Provider already registered: ${provider.name}`);
    }
    this.providers.set(provider.name, provider);
  }

  listAvailable(): ModelProvider[] {
    return [...this.providers.values()].filter((p) => p.isAvailable());
  }

  /**
   * 解析 model alias → provider。
   * 未指定 model 或无匹配 → 取第一个 available provider 的首选 model。
   */
  resolve(options: ResolveOptions = {}): { provider: ModelProvider; model: string } {
    if (options.model) {
      for (const provider of this.providers.values()) {
        if (provider.supportedModels.includes(options.model) && provider.isAvailable()) {
          return { provider, model: options.model };
        }
      }
      logger.warn(
        `Requested model '${options.model}' not available; falling back to default provider`,
      );
    }

    const fallback = this.listAvailable()[0];
    if (!fallback) {
      throw new ServiceUnavailableException(
        'No model provider available. Check LLM egress compliance flag + API keys.',
      );
    }
    return { provider: fallback, model: fallback.supportedModels[0] };
  }

  /**
   * 端到端：解析 + 调用。
   * PR3.5 起替换为 invokeWithRouting() —— 先调 ModelRouter 拿 RoutingDecision 再走 invoke。
   */
  async invoke(request: ProviderRequest, options: ResolveOptions = {}): Promise<ProviderResponse> {
    const explicitModel = options.model ?? request.model;
    const { provider, model } = this.resolve({ model: explicitModel });
    logger.log(`Routing to provider '${provider.name}' with model '${model}'`);
    return provider.invoke({ ...request, model });
  }

  /**
   * PR4a streaming：解析 + stream invoke。provider 不实现 stream 则 fallback 把 invoke
   * 结果包成单一 stop chunk。
   */
  async *invokeStream(
    request: ProviderRequest,
    options: ResolveOptions = {},
  ): AsyncGenerator<ProviderStreamChunk, void, void> {
    const explicitModel = options.model ?? request.model;
    const { provider, model } = this.resolve({ model: explicitModel });
    logger.log(`Streaming via provider '${provider.name}' model='${model}'`);
    const finalRequest = { ...request, model };
    if (provider.invokeStream) {
      yield* provider.invokeStream(finalRequest);
      return;
    }
    // fallback：非流式 provider 把 invoke 结果包一块
    const response = await provider.invoke(finalRequest);
    if (response.text) yield { type: 'text_delta', text: response.text };
    yield {
      type: 'stop',
      stopReason: response.stopReason,
      usage: response.usage,
      toolCalls: response.toolCalls,
      text: response.text,
      resolvedModel: response.resolvedModel,
    };
  }

  getProvider(name: string): ModelProvider {
    const p = this.providers.get(name);
    if (!p) throw new NotFoundException(`Provider not found: ${name}`);
    return p;
  }
}
