import { Logger } from '@nestjs/common';
import OpenAI from 'openai';
import type {
  CompletionEvent,
  CompletionParams,
  IProviderAdapter,
  ModelInfo,
  ProviderHealth,
} from '@mosaicstack/types';
import { getModelCapability } from '../model-capabilities.js';

/**
 * Default Z.ai API base URL.
 * Z.ai (BigModel / Zhipu AI) exposes an OpenAI-compatible API at this endpoint.
 * Can be overridden via the ZAI_BASE_URL environment variable.
 */
const DEFAULT_ZAI_BASE_URL = 'https://open.bigmodel.cn/api/paas/v4';

/**
 * GLM-5 model identifier on the Z.ai platform.
 */
const GLM5_MODEL_ID = 'glm-5';

/**
 * Z.ai (Zhipu AI / BigModel) provider adapter.
 *
 * Z.ai exposes an OpenAI-compatible REST API. This adapter uses the `openai`
 * SDK with a custom base URL and the ZAI_API_KEY environment variable.
 *
 * Configuration:
 *   ZAI_API_KEY   — required; Z.ai API key
 *   ZAI_BASE_URL  — optional; override the default API base URL
 */
export class ZaiAdapter implements IProviderAdapter {
  readonly name = 'zai';

  private readonly logger = new Logger(ZaiAdapter.name);
  private client: OpenAI | null = null;
  private registeredModels: ModelInfo[] = [];

  async register(): Promise<void> {
    const apiKey = process.env['ZAI_API_KEY'];
    if (!apiKey) {
      this.logger.debug('Skipping Z.ai provider registration: ZAI_API_KEY not set');
      return;
    }

    const baseURL = process.env['ZAI_BASE_URL'] ?? DEFAULT_ZAI_BASE_URL;

    this.client = new OpenAI({ apiKey, baseURL });

    this.registeredModels = this.buildModelList();
    this.logger.log(`Z.ai provider registered with ${this.registeredModels.length} model(s)`);
  }

  listModels(): ModelInfo[] {
    return this.registeredModels;
  }

  async healthCheck(): Promise<ProviderHealth> {
    const apiKey = process.env['ZAI_API_KEY'];
    if (!apiKey) {
      return {
        status: 'down',
        lastChecked: new Date().toISOString(),
        error: 'ZAI_API_KEY not configured',
      };
    }

    const baseURL = process.env['ZAI_BASE_URL'] ?? DEFAULT_ZAI_BASE_URL;
    const start = Date.now();

    try {
      const res = await fetch(`${baseURL}/models`, {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${apiKey}`,
          Accept: 'application/json',
        },
        signal: AbortSignal.timeout(5000),
      });
      const latencyMs = Date.now() - start;

      if (!res.ok) {
        return {
          status: 'degraded',
          latencyMs,
          lastChecked: new Date().toISOString(),
          error: `HTTP ${res.status}`,
        };
      }

      return { status: 'healthy', latencyMs, lastChecked: new Date().toISOString() };
    } catch (err) {
      const latencyMs = Date.now() - start;
      const error = err instanceof Error ? err.message : String(err);
      return { status: 'down', latencyMs, lastChecked: new Date().toISOString(), error };
    }
  }

  /**
   * Stream a completion through Z.ai's OpenAI-compatible API.
   */
  async *createCompletion(params: CompletionParams): AsyncIterable<CompletionEvent> {
    if (!this.client) {
      throw new Error('ZaiAdapter is not initialized. Ensure ZAI_API_KEY is set.');
    }

    const stream = await this.client.chat.completions.create({
      model: params.model,
      messages: params.messages.map((m) => ({ role: m.role, content: m.content })),
      temperature: params.temperature,
      max_tokens: params.maxTokens,
      stream: true,
    });

    let inputTokens = 0;
    let outputTokens = 0;

    for await (const chunk of stream) {
      const choice = chunk.choices[0];
      if (!choice) continue;

      const delta = choice.delta;

      if (delta.content) {
        yield { type: 'text_delta', content: delta.content };
      }

      if (choice.finish_reason === 'stop') {
        const usage = (chunk as { usage?: { prompt_tokens?: number; completion_tokens?: number } })
          .usage;
        if (usage) {
          inputTokens = usage.prompt_tokens ?? 0;
          outputTokens = usage.completion_tokens ?? 0;
        }
      }
    }

    yield {
      type: 'done',
      usage: { inputTokens, outputTokens },
    };
  }

  // ---------------------------------------------------------------------------
  // Private helpers
  // ---------------------------------------------------------------------------

  private buildModelList(): ModelInfo[] {
    const capability = getModelCapability(GLM5_MODEL_ID);

    if (!capability) {
      this.logger.warn(`Model capability entry not found for '${GLM5_MODEL_ID}'; using defaults`);
      return [
        {
          id: GLM5_MODEL_ID,
          provider: 'zai',
          name: 'GLM-5',
          reasoning: false,
          contextWindow: 128000,
          maxTokens: 8192,
          inputTypes: ['text'],
          cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
        },
      ];
    }

    return [
      {
        id: capability.id,
        provider: 'zai',
        name: capability.displayName,
        reasoning: capability.capabilities.reasoning,
        contextWindow: capability.contextWindow,
        maxTokens: capability.maxOutputTokens,
        inputTypes: capability.capabilities.vision ? ['text', 'image'] : ['text'],
        cost: {
          input: capability.costPer1kInput ?? 0,
          output: capability.costPer1kOutput ?? 0,
          cacheRead: 0,
          cacheWrite: 0,
        },
      },
    ];
  }
}
