import { fetch } from 'undici';
import { createModuleLogger } from '../../utils/logger.js';
import type { TelegramBotInfo, SendPaidMediaRequest } from '../../types/telegram.js';

const logger = createModuleLogger('telegram-bot-client');

const TELEGRAM_API_BASE = 'https://api.telegram.org/bot';
const MAX_RETRIES = 3;
const INITIAL_BACKOFF_MS = 1000;

export interface SendMessageOptions {
  parse_mode?: 'HTML' | 'Markdown' | 'MarkdownV2';
  reply_to_message_id?: number;
  disable_notification?: boolean;
}

export interface CreateChatInviteLinkOptions {
  name?: string;
  expire_date?: number;
  member_limit?: number;
}

export interface SetWebhookOptions {
  certificate?: string;
  max_connections?: number;
  allowed_updates?: string[];
  secret_token?: string;
}

export interface TelegramBotClientOptions {
  initialBackoffMs?: number;
}

export interface TelegramApiResponse<T = unknown> {
  ok: boolean;
  result?: T;
  description?: string;
  error_code?: number;
}

export class TelegramBotClient {
  private readonly token: string;
  private readonly baseUrl: string;
  private readonly initialBackoffMs: number;

  constructor(token: string, options?: TelegramBotClientOptions) {
    if (!token) {
      throw new Error('Telegram bot token is required');
    }
    this.token = token;
    this.baseUrl = `${TELEGRAM_API_BASE}${token}`;
    this.initialBackoffMs = options?.initialBackoffMs ?? INITIAL_BACKOFF_MS;
  }

  async getMe(): Promise<TelegramBotInfo> {
    const response = await this.callApi<TelegramBotInfo>('getMe');
    return response;
  }

  async sendMessage(
    chatId: number | string,
    text: string,
    options?: SendMessageOptions,
  ): Promise<unknown> {
    return this.callApi('sendMessage', {
      chat_id: chatId,
      text,
      ...options,
    });
  }

  async sendChatAction(chatId: number | string, action: string): Promise<boolean> {
    return this.callApi<boolean>('sendChatAction', {
      chat_id: chatId,
      action,
    });
  }

  async sendPaidMedia(request: SendPaidMediaRequest): Promise<unknown> {
    return this.callApi('sendPaidMedia', { ...request });
  }

  async sendInvoice(params: {
    chat_id: number;
    title: string;
    description: string;
    payload: string;
    currency: string;
    prices: Array<{ label: string; amount: number }>;
  }): Promise<unknown> {
    return this.callApi('sendInvoice', params);
  }

  async sendPhoto(
    chatId: number | string,
    photo: string,
    caption?: string,
  ): Promise<unknown> {
    return this.callApi('sendPhoto', {
      chat_id: chatId,
      photo,
      ...(caption ? { caption } : {}),
    });
  }

  async createChatInviteLink(
    chatId: number | string,
    options?: CreateChatInviteLinkOptions,
  ): Promise<unknown> {
    return this.callApi('createChatInviteLink', {
      chat_id: chatId,
      ...options,
    });
  }

  async setWebhook(url: string, options?: SetWebhookOptions): Promise<boolean> {
    return this.callApi<boolean>('setWebhook', {
      url,
      ...options,
    });
  }

  async deleteWebhook(): Promise<boolean> {
    return this.callApi<boolean>('deleteWebhook');
  }

  async answerPreCheckoutQuery(
    preCheckoutQueryId: string,
    ok: boolean,
    errorMessage?: string,
  ): Promise<boolean> {
    return this.callApi<boolean>('answerPreCheckoutQuery', {
      pre_checkout_query_id: preCheckoutQueryId,
      ok,
      ...(errorMessage !== undefined && { error_message: errorMessage }),
    });
  }

  private async callApi<T>(method: string, params?: Record<string, unknown>): Promise<T> {
    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
      try {
        logger.debug({ attempt, method }, 'Sending Telegram API request');

        const url = `${this.baseUrl}/${method}`;
        const fetchOptions: Record<string, unknown> = {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
        };

        if (params !== undefined) {
          fetchOptions.body = JSON.stringify(params);
        }

        const response = await fetch(url, fetchOptions);
        const data = (await response.json()) as TelegramApiResponse<T>;

        if (!data.ok) {
          const err = new Error(
            `Telegram API error: ${data.error_code ?? response.status} - ${data.description ?? 'Unknown error'}`,
          );
          logger.warn(
            { status: data.error_code, attempt, description: data.description },
            'Telegram API request failed',
          );

          // Don't retry on client errors (4xx) except 429 (rate limit)
          if (
            data.error_code !== undefined &&
            data.error_code >= 400 &&
            data.error_code < 500 &&
            data.error_code !== 429
          ) {
            throw err;
          }
          lastError = err;
        } else {
          logger.debug({ method }, 'Telegram API request successful');
          return data.result as T;
        }
      } catch (error) {
        if (lastError === undefined || lastError !== error) {
          lastError = error instanceof Error ? error : new Error(String(error));
        }
        if (
          lastError.message.includes('Telegram API error: 4') &&
          !lastError.message.includes('429')
        ) {
          throw lastError;
        }
        logger.warn({ attempt, error: lastError.message }, 'Telegram request attempt failed');
      }

      if (attempt < MAX_RETRIES) {
        const backoff = this.initialBackoffMs * Math.pow(2, attempt - 1);
        logger.debug({ backoff, attempt }, 'Waiting before retry');
        await sleep(backoff);
      }
    }

    logger.error({ error: lastError?.message }, 'All Telegram API retry attempts exhausted');
    throw lastError ?? new Error('Telegram API request failed after all retries');
  }
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
