import Anthropic from '@anthropic-ai/sdk';
import {
  AIProvider,
  ProviderOptions,
  ProviderResult,
  StreamChunk,
  ProviderConfig,
  ProviderError,
  ProviderUnavailableError,
  ProviderRateLimitError,
  ToolDefinition,
  ToolCall,
} from './provider.js';

export class AnthropicProvider implements AIProvider {
  readonly name = 'Anthropic';
  readonly type = 'claude' as const;

  private client: Anthropic;
  private config: ProviderConfig;
  private defaultModel: string;

  constructor(config: ProviderConfig = {}) {
    this.config = config;
    this.defaultModel = config.model || 'claude-sonnet-4-20250514';

    this.client = new Anthropic({
      apiKey: config.apiKey || process.env.ANTHROPIC_API_KEY,
      baseURL: config.baseUrl,
      timeout: config.timeout || 120000,
      maxRetries: config.maxRetries || 2,
    });
  }

  async execute(
    prompt: string,
    options?: ProviderOptions,
  ): Promise<ProviderResult> {
    if (!this.isAvailable()) {
      throw new ProviderUnavailableError(this.name, 'API key not configured');
    }

    try {
      const response = await this.client.messages.create({
        model: options?.model || this.defaultModel,
        max_tokens: options?.maxTokens || 4096,
        temperature: options?.temperature,
        system: options?.systemPrompt,
        stop_sequences: options?.stopSequences,
        messages: [{ role: 'user', content: prompt }],
        tools: options?.tools ? this.convertTools(options.tools) : undefined,
      });

      return this.parseResponse(response);
    } catch (error) {
      throw this.handleError(error);
    }
  }

  async *stream(
    prompt: string,
    options?: ProviderOptions,
  ): AsyncIterable<StreamChunk> {
    if (!this.isAvailable()) {
      throw new ProviderUnavailableError(this.name, 'API key not configured');
    }

    try {
      const stream = this.client.messages.stream({
        model: options?.model || this.defaultModel,
        max_tokens: options?.maxTokens || 4096,
        temperature: options?.temperature,
        system: options?.systemPrompt,
        stop_sequences: options?.stopSequences,
        messages: [{ role: 'user', content: prompt }],
        tools: options?.tools ? this.convertTools(options.tools) : undefined,
      });

      let inputTokens = 0;
      let outputTokens = 0;

      for await (const event of stream) {
        if (
          event.type === 'content_block_delta' &&
          event.delta.type === 'text_delta'
        ) {
          yield { type: 'text', delta: event.delta.text };
        } else if (
          event.type === 'content_block_start' &&
          event.content_block.type === 'tool_use'
        ) {
          yield {
            type: 'tool_use',
            toolCall: {
              id: event.content_block.id,
              name: event.content_block.name,
              arguments: event.content_block.input as Record<string, unknown>,
            },
          };
        } else if (event.type === 'message_start') {
          inputTokens = event.message.usage.input_tokens;
        } else if (event.type === 'message_delta') {
          outputTokens = event.usage?.output_tokens || 0;
        }
      }

      yield { type: 'usage', usage: { inputTokens, outputTokens } };
    } catch (error) {
      throw this.handleError(error);
    }
  }

  isAvailable(): boolean {
    return !!(
      this.config.apiKey ||
      process.env.ANTHROPIC_API_KEY ||
      process.env.AGENT_CODE_OAUTH_TOKEN
    );
  }

  async getModels(): Promise<string[]> {
    return [
      'claude-opus-4-20250514',
      'claude-sonnet-4-20250514',
      'claude-haiku-3-5-20241022',
      'claude-3-5-sonnet-20241022',
      'claude-3-5-haiku-20241022',
      'claude-3-opus-20240229',
      'claude-3-sonnet-20240229',
      'claude-3-haiku-20240307',
    ];
  }

  getDefaultModel(): string {
    return this.defaultModel;
  }

  private convertTools(tools: ToolDefinition[]): Anthropic.Tool[] {
    return tools.map((tool) => ({
      name: tool.name,
      description: tool.description,
      input_schema: tool.parameters as Anthropic.Tool['input_schema'],
    }));
  }

  private parseResponse(response: Anthropic.Message): ProviderResult {
    let content = '';
    const toolCalls: ToolCall[] = [];

    for (const block of response.content) {
      if (block.type === 'text') {
        content += block.text;
      } else if (block.type === 'tool_use') {
        toolCalls.push({
          id: block.id,
          name: block.name,
          arguments: block.input as Record<string, unknown>,
        });
      }
    }

    return {
      content,
      toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
      usage: {
        inputTokens: response.usage.input_tokens,
        outputTokens: response.usage.output_tokens,
      },
      stopReason: response.stop_reason as ProviderResult['stopReason'],
    };
  }

  private handleError(error: unknown): Error {
    if (error instanceof Anthropic.APIError) {
      if (error.status === 429) {
        const retryAfter = error.headers?.['retry-after'];
        return new ProviderRateLimitError(
          this.name,
          retryAfter ? parseInt(retryAfter, 10) : undefined,
        );
      }
      return new ProviderError(
        error.message,
        this.name,
        error.code,
        error.status,
      );
    }
    if (error instanceof Error) {
      return new ProviderError(error.message, this.name);
    }
    return new ProviderError('Unknown error', this.name);
  }
}
