import type {
    TelegramApiResponse,
    TelegramMessage,
    TelegramUpdate,
    TelegramUser,
} from './types';
import { TELEGRAM_MAX_TEXT_LENGTH } from './types';
import { logError, logInfo } from '../../utils';

/**
 * Thin HTTP wrapper around the Telegram Bot API.
 *
 * Only the three endpoints the bot loop needs are exposed:
 *   - getMe()          — token validity probe (used by the "test connection" command)
 *   - getUpdates()     — long-polling driver
 *   - sendMessage()    — outbound replies
 *
 * Design notes:
 *   - Uses native `fetch` so we don't pull axios in just for this integration.
 *   - All errors are normalized to `TelegramClientError` so callers can branch
 *     on `kind` (`network` / `api` / `aborted`) without inspecting raw fetch
 *     internals.
 *   - The `signal` parameter is honored on every call — long-polling depends on
 *     this for clean shutdown when the bot is disabled or the extension
 *     deactivates.
 *   - Tokens are passed by reference (`getToken: () => string | undefined`)
 *     instead of stored, so rotating the SecretStorage value takes effect on
 *     the next request without rebuilding the client.
 */

export type TelegramClientErrorKind = 'network' | 'api' | 'aborted' | 'no-token';

export class TelegramClientError extends Error {
    constructor(
        public readonly kind: TelegramClientErrorKind,
        message: string,
        public readonly statusCode?: number
    ) {
        super(message);
        this.name = 'TelegramClientError';
    }
}

export interface SendMessageOptions {
    chatId: number;
    text: string;
    /** Defaults to "Markdown" for clean formatting; pass null to disable. */
    parseMode?: 'Markdown' | 'MarkdownV2' | 'HTML' | null;
    signal?: AbortSignal;
}

export interface GetUpdatesOptions {
    offset?: number;
    /** Long-poll seconds. 0 = short poll. Telegram caps at 50; we default to 25. */
    timeoutSec?: number;
    signal?: AbortSignal;
}

export interface ITelegramClient {
    getMe(signal?: AbortSignal): Promise<TelegramUser>;
    getUpdates(opts: GetUpdatesOptions): Promise<TelegramUpdate[]>;
    sendMessage(opts: SendMessageOptions): Promise<TelegramMessage>;
}

export interface TelegramHttpClientDeps {
    /** Returns the current bot token (or empty when not configured). */
    getToken: () => string | undefined;
    /** Optional fetch override for tests. */
    fetchImpl?: typeof fetch;
}

export class TelegramHttpClient implements ITelegramClient {
    private readonly _fetch: typeof fetch;

    constructor(private readonly _deps: TelegramHttpClientDeps) {
        this._fetch = _deps.fetchImpl ?? fetch;
    }

    async getMe(signal?: AbortSignal): Promise<TelegramUser> {
        return this._call<TelegramUser>('getMe', undefined, signal);
    }

    async getUpdates(opts: GetUpdatesOptions): Promise<TelegramUpdate[]> {
        const body: Record<string, unknown> = {
            timeout: Math.max(0, Math.min(opts.timeoutSec ?? 25, 50)),
        };
        if (typeof opts.offset === 'number') body.offset = opts.offset;
        return this._call<TelegramUpdate[]>('getUpdates', body, opts.signal);
    }

    async sendMessage(opts: SendMessageOptions): Promise<TelegramMessage> {
        const text = truncateForTelegram(opts.text);
        const body: Record<string, unknown> = {
            chat_id: opts.chatId,
            text,
            disable_web_page_preview: true,
        };
        const parseMode = opts.parseMode === undefined ? 'Markdown' : opts.parseMode;
        if (parseMode) body.parse_mode = parseMode;
        return this._call<TelegramMessage>('sendMessage', body, opts.signal);
    }

    private async _call<T>(
        method: string,
        body: Record<string, unknown> | undefined,
        signal?: AbortSignal
    ): Promise<T> {
        const token = (this._deps.getToken() || '').trim();
        if (!token) {
            throw new TelegramClientError('no-token', 'Telegram bot token is not configured.');
        }

        const url = `https://api.telegram.org/bot${token}/${method}`;
        let response: Response;
        try {
            response = await this._fetch(url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: body ? JSON.stringify(body) : undefined,
                signal,
            });
        } catch (e: any) {
            if (e?.name === 'AbortError' || signal?.aborted) {
                throw new TelegramClientError('aborted', 'Request aborted.');
            }
            const msg = e?.message ?? String(e);
            logError('Telegram API network error.', { method, error: msg });
            throw new TelegramClientError('network', `Network error calling ${method}: ${msg}`);
        }

        let parsed: TelegramApiResponse<T>;
        try {
            parsed = (await response.json()) as TelegramApiResponse<T>;
        } catch (e: any) {
            throw new TelegramClientError('api', `Telegram API returned non-JSON for ${method}: ${e?.message ?? e}`);
        }

        if (!parsed.ok) {
            logError('Telegram API error response.', { method, error_code: parsed.error_code, description: parsed.description });
            throw new TelegramClientError('api', `${method} failed: ${parsed.description}`, parsed.error_code);
        }

        logInfo('Telegram API call succeeded.', { method, status: response.status });
        return parsed.result;
    }
}

/** Truncate text to Telegram's 4096-char limit, preserving a trailing ellipsis hint. */
export function truncateForTelegram(text: string): string {
    if (typeof text !== 'string') return '';
    if (text.length <= TELEGRAM_MAX_TEXT_LENGTH) return text;
    const ellipsis = '\n\n... (truncated)';
    return text.slice(0, TELEGRAM_MAX_TEXT_LENGTH - ellipsis.length) + ellipsis;
}
