import { Logger } from '@nestjs/common';
import Anthropic from '@anthropic-ai/sdk';
import type { ModelRegistry } from '@mariozechner/pi-coding-agent';
import type {
  CompletionEvent,
  CompletionParams,
  IProviderAdapter,
  ModelInfo,
  ProviderHealth,
} from '@mosaicstack/types';

/**
 * Anthropic provider adapter.
 *
 * Registers Claude models with the Pi ModelRegistry via the Anthropic SDK.
 * Configuration is driven by environment variables:
 *   ANTHROPIC_API_KEY — Anthropic API key (required)
 */
export class AnthropicAdapter implements IProviderAdapter {
  readonly name = 'anthropic';

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

  constructor(private readonly registry: ModelRegistry) {}

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

    this.client = new Anthropic({ apiKey });

    const models: ModelInfo[] = [
      {
        id: 'claude-opus-4-6',
        provider: 'anthropic',
        name: 'Claude Opus 4.6',
        reasoning: true,
        contextWindow: 200000,
        maxTokens: 32000,
        inputTypes: ['text', 'image'],
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      },
      {
        id: 'claude-sonnet-4-6',
        provider: 'anthropic',
        name: 'Claude Sonnet 4.6',
        reasoning: true,
        contextWindow: 200000,
        maxTokens: 16000,
        inputTypes: ['text', 'image'],
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      },
      {
        id: 'claude-haiku-4-5',
        provider: 'anthropic',
        name: 'Claude Haiku 4.5',
        reasoning: false,
        contextWindow: 200000,
        maxTokens: 8192,
        inputTypes: ['text', 'image'],
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      },
    ];

    this.registry.registerProvider('anthropic', {
      apiKey,
      baseUrl: 'https://api.anthropic.com',
      api: 'anthropic' as never,
      models: models.map((m) => ({
        id: m.id,
        name: m.name,
        reasoning: m.reasoning,
        input: m.inputTypes as ('text' | 'image')[],
        cost: m.cost,
        contextWindow: m.contextWindow,
        maxTokens: m.maxTokens,
      })),
    });

    this.registeredModels = models;

    this.logger.log(
      `Anthropic provider registered with models: ${models.map((m) => m.id).join(', ')}`,
    );
  }

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

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

    const start = Date.now();

    try {
      const client = this.client ?? new Anthropic({ apiKey });
      await client.models.list({ limit: 1 });
      const latencyMs = Date.now() - start;
      return { status: 'healthy', latencyMs, lastChecked: new Date().toISOString() };
    } catch (err) {
      const latencyMs = Date.now() - start;
      const error = err instanceof Error ? err.message : String(err);
      const status = error.includes('401') || error.includes('403') ? 'degraded' : 'down';
      return { status, latencyMs, lastChecked: new Date().toISOString(), error };
    }
  }

  /**
   * Stream a completion from Anthropic using the messages API.
   * Maps Anthropic streaming events to the CompletionEvent format.
   *
   * Note: Currently reserved for future direct-completion use. The Pi SDK
   * integration routes completions through ModelRegistry / AgentSession.
   */
  async *createCompletion(params: CompletionParams): AsyncIterable<CompletionEvent> {
    const apiKey = process.env['ANTHROPIC_API_KEY'];
    if (!apiKey) {
      throw new Error('AnthropicAdapter: ANTHROPIC_API_KEY not configured');
    }

    const client = this.client ?? new Anthropic({ apiKey });

    // Separate system messages from user/assistant messages
    const systemMessages = params.messages.filter((m) => m.role === 'system');
    const conversationMessages = params.messages.filter((m) => m.role !== 'system');

    const systemPrompt =
      systemMessages.length > 0 ? systemMessages.map((m) => m.content).join('\n') : undefined;

    const stream = await client.messages.stream({
      model: params.model,
      max_tokens: params.maxTokens ?? 1024,
      ...(systemPrompt !== undefined ? { system: systemPrompt } : {}),
      messages: conversationMessages.map((m) => ({
        role: m.role as 'user' | 'assistant',
        content: m.content,
      })),
      ...(params.temperature !== undefined ? { temperature: params.temperature } : {}),
      ...(params.tools && params.tools.length > 0
        ? {
            tools: params.tools.map((t) => ({
              name: t.name,
              description: t.description,
              input_schema: t.parameters as Anthropic.Tool['input_schema'],
            })),
          }
        : {}),
    });

    for await (const event of stream) {
      if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
        yield { type: 'text_delta', content: event.delta.text };
      } else if (event.type === 'content_block_delta' && event.delta.type === 'input_json_delta') {
        yield { type: 'tool_call', name: '', arguments: event.delta.partial_json };
      } else if (event.type === 'message_delta' && event.usage) {
        yield {
          type: 'done',
          usage: {
            inputTokens:
              (event as { usage: { input_tokens?: number; output_tokens: number } }).usage
                .input_tokens ?? 0,
            outputTokens: event.usage.output_tokens,
          },
        };
      }
    }

    // Emit final done event with full usage from the completed message
    const finalMessage = await stream.finalMessage();
    yield {
      type: 'done',
      usage: {
        inputTokens: finalMessage.usage.input_tokens,
        outputTokens: finalMessage.usage.output_tokens,
      },
    };
  }
}
