import { randomBytes } from 'node:crypto';

import { config } from '../config.js';
import { prisma } from '../prisma.js';
import {
  sha256Hex,
  upsertTelegramUser,
  type TelegramUserPayload,
} from './telegram.js';

type TelegramUpdate = {
  message?: {
    chat: { id: number };
    text?: string;
    from?: TelegramBotUser;
  };
};

type TelegramBotUser = {
  id: number;
  is_bot?: boolean;
  first_name?: string;
  last_name?: string;
  username?: string;
  language_code?: string;
};

type TelegramProfilePhotos = {
  photos: { file_id: string }[][];
};

type TelegramFile = {
  file_path: string;
};

type TelegramSentMessage = {
  message_id: number;
};

const loginPrefix = 'login_';
const loginExpirationHandles = new Map<string, ReturnType<typeof setTimeout>>();

function randomToken() {
  return randomBytes(32).toString('base64url');
}

function expiresIn(seconds: number) {
  return new Date(Date.now() + seconds * 1000);
}

function botApiUrl(method: string) {
  if (!config.telegramMiniAppBotToken) {
    throw new Error('TELEGRAM_MINI_APP_BOT_TOKEN is required.');
  }

  return `https://api.telegram.org/bot${config.telegramMiniAppBotToken}/${method}`;
}

async function callTelegram<T>(method: string, body: Record<string, unknown>) {
  const response = await fetch(botApiUrl(method), {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(body),
  });

  if (!response.ok) {
    throw new Error(`Telegram ${method} failed with ${response.status}.`);
  }

  const payload = (await response.json()) as {
    ok: boolean;
    description?: string;
    result?: T;
  };
  if (!payload.ok) {
    throw new Error(payload.description ?? `Telegram ${method} failed.`);
  }

  return payload.result as T;
}

function userPayload(from: TelegramBotUser): TelegramUserPayload {
  return {
    id: from.id,
    username: from.username,
    firstName: from.first_name,
    lastName: from.last_name,
    languageCode: from.language_code,
  };
}

async function telegramUserPhotoUrl(userId: number) {
  const photos = await callTelegram<TelegramProfilePhotos>('getUserProfilePhotos', {
    user_id: userId,
    limit: 1,
  });
  const variants = photos.photos[0];
  const fileId = variants?.[variants.length - 1]?.file_id;
  return fileId ? `${config.publicApiUrl}/telegram/photo/${fileId}` : undefined;
}

export async function fetchTelegramPhoto(fileId: string) {
  const file = await callTelegram<TelegramFile>('getFile', { file_id: fileId });
  const response = await fetch(
    `https://api.telegram.org/file/bot${config.telegramMiniAppBotToken}/${file.file_path}`,
  );

  if (!response.ok) {
    throw new Error(`Telegram file fetch failed with ${response.status}.`);
  }

  return {
    contentType: file.file_path.toLowerCase().endsWith('.png')
      ? 'image/png'
      : 'image/jpeg',
    bytes: Buffer.from(await response.arrayBuffer()),
  };
}

function formatRemaining(expiresAt: Date) {
  const seconds = Math.max(0, Math.ceil((expiresAt.getTime() - Date.now()) / 1000));
  const minutes = Math.floor(seconds / 60).toString();
  const rest = (seconds % 60).toString().padStart(2, '0');
  return `${minutes}:${rest}`;
}

function loginReplyMarkup(token: string, expiresAt: Date) {
  return {
    inline_keyboard: [
      [
        {
          text: `${formatRemaining(expiresAt)}  Войти`,
          url: `${config.webAppUrl}?telegram_login=${encodeURIComponent(token)}`,
        },
      ],
    ],
  };
}

function loginMessageText(expiresAt: Date) {
  return 'Чтобы войти на сайт map.craftee.vn, воспользуйтесь ссылкой ниже.';
}

async function sendLoginMessage(
  chatId: number,
  text: string,
  token?: string,
  expiresAt?: Date,
) {
  const replyMarkup = token && expiresAt ? loginReplyMarkup(token, expiresAt) : undefined;

  return callTelegram<TelegramSentMessage>('sendMessage', {
    chat_id: chatId,
    text,
    reply_markup: replyMarkup,
  });
}

async function editLoginMessage(
  chatId: string,
  messageId: number,
  text: string,
) {
  await callTelegram('editMessageText', {
    chat_id: chatId,
    message_id: messageId,
    text,
    reply_markup: { inline_keyboard: [] },
  });
}

function scheduleLoginExpiration(
  requestId: string,
  chatId: string,
  messageId: number,
  expiresAt: Date,
) {
  const existingHandle = loginExpirationHandles.get(requestId);
  if (existingHandle) {
    clearTimeout(existingHandle);
  }

  const handle = setTimeout(() => {
    void (async () => {
      const updated = await prisma.telegramLoginRequest.updateMany({
        where: { id: requestId, status: 'CONFIRMED' },
        data: { status: 'EXPIRED' },
      });

      loginExpirationHandles.delete(requestId);
      if (updated.count > 0) {
        await editLoginMessage(chatId, messageId, 'Ссылка входа устарела.');
      }
    })();
  }, Math.max(0, expiresAt.getTime() - Date.now()));
  loginExpirationHandles.set(requestId, handle);
}

function cancelLoginExpiration(requestId: string) {
  const handle = loginExpirationHandles.get(requestId);
  if (handle) {
    clearTimeout(handle);
    loginExpirationHandles.delete(requestId);
  }
}

async function expireLoginMessageNow(
  requestId: string,
  chatId: string,
  messageId: number,
) {
  cancelLoginExpiration(requestId);
  const updated = await prisma.telegramLoginRequest.updateMany({
    where: { id: requestId, status: 'CONFIRMED' },
    data: { status: 'EXPIRED' },
  });

  if (updated.count > 0) {
    await editLoginMessage(chatId, messageId, 'Ссылка входа устарела.');
  }
}

async function refreshExpiredLoginMessage(
  requestId: string,
  chatId: string,
  messageId: number,
) {
  const request = await prisma.telegramLoginRequest.findUnique({
    where: { id: requestId },
    select: { status: true },
  });

  if (request?.status === 'CONFIRMED') {
    await expireLoginMessageNow(requestId, chatId, messageId);
  }
}

export async function createTelegramBotLogin() {
  const token = randomToken();
  const expiresAt = expiresIn(config.botLoginMaxAgeSeconds);

  await prisma.telegramLoginRequest.create({
    data: {
      tokenHash: sha256Hex(token),
      expiresAt,
    },
  });

  return {
    token,
    botUrl: `https://t.me/${config.telegramBotUsername}?start=${loginPrefix}${token}`,
    expiresAt: expiresAt.toISOString(),
  };
}

export async function completeTelegramBotLogin(token: string) {
  const request = await prisma.telegramLoginRequest.findUnique({
    where: { tokenHash: sha256Hex(token) },
    include: { user: true },
  });

  if (!request) {
    throw new Error('Telegram login token is invalid.');
  }

  if (request.expiresAt <= new Date()) {
    if (request.telegramChatId && request.telegramMessageId) {
      await refreshExpiredLoginMessage(
        request.id,
        request.telegramChatId,
        request.telegramMessageId,
      );
    } else {
      await prisma.telegramLoginRequest.update({
        where: { id: request.id },
        data: { status: 'EXPIRED' },
      });
    }
    throw new Error('Telegram login token is expired.');
  }

  if (request.status !== 'CONFIRMED' || !request.sessionToken || !request.user) {
    throw new Error('Telegram login is not confirmed.');
  }

  cancelLoginExpiration(request.id);
  await prisma.telegramLoginRequest.update({
    where: { id: request.id },
    data: { status: 'USED' },
  });
  if (request.telegramChatId && request.telegramMessageId) {
    await editLoginMessage(
      request.telegramChatId,
      request.telegramMessageId,
      'Вход выполнен.\nМожно вернуться в MapFlow.',
    );
  }

  return {
    sessionToken: request.sessionToken,
    user: request.user,
  };
}

export async function handleTelegramBotWebhook(
  update: TelegramUpdate,
  secretToken?: string,
) {
  if (config.telegramWebhookSecret && secretToken !== config.telegramWebhookSecret) {
    throw new Error('Telegram webhook secret is invalid.');
  }

  const message = update.message;
  const text = message?.text;
  const from = message?.from;
  const chatId = message?.chat.id;
  if (!message || !text || !from || !chatId) {
    return;
  }

  const [command, payload] = text.split(' ');
  if (command !== '/start' || !payload?.startsWith(loginPrefix)) {
    await sendLoginMessage(chatId, 'Начните вход с сайта MapFlow.');
    return;
  }

  const token = payload.slice(loginPrefix.length);
  const request = await prisma.telegramLoginRequest.findUnique({
    where: { tokenHash: sha256Hex(token) },
  });

  if (!request || request.status !== 'PENDING' || request.expiresAt <= new Date()) {
    await sendLoginMessage(
      chatId,
      'Ссылка входа устарела.\nВернитесь на сайт и начните вход заново.',
    );
    return;
  }

  const user = await upsertTelegramUser({
    ...userPayload(from),
    photoUrl: await telegramUserPhotoUrl(from.id),
  });
  const sessionToken = randomToken();
  await prisma.userSession.create({
    data: {
      tokenHash: sha256Hex(sessionToken),
      userId: user.id,
      expiresAt: expiresIn(config.sessionMaxAgeSeconds),
    },
  });

  const sentMessage = await sendLoginMessage(
    chatId,
    loginMessageText(request.expiresAt),
    token,
    request.expiresAt,
  );

  await prisma.telegramLoginRequest.update({
    where: { id: request.id },
    data: {
      status: 'CONFIRMED',
      sessionToken,
      telegramChatId: chatId.toString(),
      telegramMessageId: sentMessage.message_id,
      userId: user.id,
    },
  });

  scheduleLoginExpiration(
    request.id,
    chatId.toString(),
    sentMessage.message_id,
    request.expiresAt,
  );
}
