import { Injectable, OnModuleInit, Logger, ServiceUnavailableException } from "@nestjs/common";
import { LlmManagerService } from "./llm-manager.service";
import type { ChatRequestDto, ChatResponseDto, EmbedRequestDto, EmbedResponseDto } from "./dto";
import type { LlmProviderHealthStatus } from "./providers/llm-provider.interface";

/**
 * LLM Service
 *
 * High-level service for LLM operations. Delegates to providers via LlmManagerService.
 * Maintains backward compatibility with the original API while supporting multiple providers.
 *
 * @example
 * ```typescript
 * // Chat completion
 * const response = await llmService.chat({
 *   model: "llama3.2",
 *   messages: [{ role: "user", content: "Hello" }]
 * });
 *
 * // Streaming chat
 * for await (const chunk of llmService.chatStream(request)) {
 *   console.log(chunk.message.content);
 * }
 *
 * // Generate embeddings
 * const embeddings = await llmService.embed({
 *   model: "llama3.2",
 *   input: ["text to embed"]
 * });
 * ```
 */
@Injectable()
export class LlmService implements OnModuleInit {
  private readonly logger = new Logger(LlmService.name);

  constructor(private readonly llmManager: LlmManagerService) {
    this.logger.log("LLM service initialized");
  }

  /**
   * Check health status on module initialization.
   * Logs the status but does not fail if unhealthy.
   */
  async onModuleInit(): Promise<void> {
    const health = await this.checkHealth();
    if (health.healthy) {
      const endpoint = health.endpoint ?? "default endpoint";
      this.logger.log(`LLM provider healthy: ${health.provider} at ${endpoint}`);
    } else {
      const errorMsg = health.error ?? "unknown error";
      this.logger.warn(`LLM provider unhealthy: ${errorMsg}`);
    }
  }
  /**
   * Check health of the default LLM provider.
   * Returns health status without throwing errors.
   *
   * @returns Health status of the default provider
   */
  async checkHealth(): Promise<LlmProviderHealthStatus> {
    try {
      const provider = await this.llmManager.getDefaultProvider();
      return await provider.checkHealth();
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error(`Health check failed: ${errorMessage}`);
      return {
        healthy: false,
        provider: "unknown",
        error: errorMessage,
      };
    }
  }
  /**
   * List all available models from the default provider.
   *
   * @returns Array of model names
   * @throws {ServiceUnavailableException} If provider is unavailable or request fails
   */
  async listModels(): Promise<string[]> {
    try {
      const provider = await this.llmManager.getDefaultProvider();
      return await provider.listModels();
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error(`Failed to list models: ${errorMessage}`);
      throw new ServiceUnavailableException(`Failed to list models: ${errorMessage}`);
    }
  }
  /**
   * Perform a synchronous chat completion.
   *
   * @param request - Chat request with messages and configuration
   * @returns Complete chat response
   * @throws {ServiceUnavailableException} If provider is unavailable or request fails
   */
  async chat(request: ChatRequestDto): Promise<ChatResponseDto> {
    try {
      const provider = await this.llmManager.getDefaultProvider();
      return await provider.chat(request);
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error(`Chat failed: ${errorMessage}`);
      throw new ServiceUnavailableException(`Chat completion failed: ${errorMessage}`);
    }
  }
  /**
   * Perform a streaming chat completion.
   * Yields response chunks as they arrive from the provider.
   *
   * @param request - Chat request with messages and configuration
   * @yields Chat response chunks
   * @throws {ServiceUnavailableException} If provider is unavailable or request fails
   */
  async *chatStream(request: ChatRequestDto): AsyncGenerator<ChatResponseDto, void, unknown> {
    try {
      const provider = await this.llmManager.getDefaultProvider();
      const stream = provider.chatStream(request);

      for await (const chunk of stream) {
        yield chunk;
      }
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error(`Stream failed: ${errorMessage}`);
      throw new ServiceUnavailableException(`Streaming failed: ${errorMessage}`);
    }
  }
  /**
   * Generate embeddings for the given input texts.
   *
   * @param request - Embedding request with model and input texts
   * @returns Embeddings response with vector arrays
   * @throws {ServiceUnavailableException} If provider is unavailable or request fails
   */
  async embed(request: EmbedRequestDto): Promise<EmbedResponseDto> {
    try {
      const provider = await this.llmManager.getDefaultProvider();
      return await provider.embed(request);
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error(`Embed failed: ${errorMessage}`);
      throw new ServiceUnavailableException(`Embedding failed: ${errorMessage}`);
    }
  }
}
