import { findUserByEmailAndPassword, touchLastLogin, findUserById, createUser, userExistsByEmail } from '../repositories/user.repository.js';
import { createAuthSession, revokeSession, getAuthSession, rotateSessionTokens } from '../repositories/session.repository.js';
import {
  generateRefreshToken,
  generateCsrfToken,
  hashToken,
  signAccessToken,
  verifyRefreshToken,
  signRefreshToken,
} from './token.service.js';

const REFRESH_TTL_DAYS = Number(process.env.REFRESH_TTL_DAYS || 30);

function buildSessionExpiresAt() {
  const dt = new Date();
  dt.setDate(dt.getDate() + REFRESH_TTL_DAYS);
  return dt;
}

export async function loginUser({
  email,
  password,
  userAgent,
  ipAddress,
}) {
  const user = await findUserByEmailAndPassword(email, password);

  if (!user) {
    const err = new Error('Invalid email or password');
    err.statusCode = 401;
    err.code = 'INVALID_CREDENTIALS';
    throw err;
  }

  const refreshToken = generateRefreshToken();
  const csrfToken = generateCsrfToken();

  const refreshTokenHash = hashToken(refreshToken);
  const csrfTokenHash = hashToken(csrfToken);

  const session = await createAuthSession({
    userId: user.id,
    refreshTokenHash,
    csrfTokenHash,
    userAgent,
    ipAddress,
    expiresAt: buildSessionExpiresAt(),
  });

  const accessToken = signAccessToken(user, session.id);

  await touchLastLogin(user.id);

  return {
    user: {
      id: user.id,
      email: user.email,
      displayName: user.display_name,
      role: user.role,
      status: user.status,
    },
    session: {
      id: session.id,
      expiresAt: session.expires_at,
    },
    accessToken,
    refreshToken,
    csrfToken,
  };
}

export async function logoutUser({ sessionId }) {
  if (!sessionId) {
    return;
  }

  await revokeSession(sessionId);
}

export async function refreshUserSession({ refreshToken, userAgent, ipAddress }) {
  let payload;
  try {
    payload = verifyRefreshToken(refreshToken);
  } catch (err) {
    const error = new Error('Invalid or expired refresh token');
    error.code = 'INVALID_REFRESH_TOKEN';
    error.status = 401;
    throw error;
  }

  const session = await getAuthSession(payload.sid);
  if (!session) {
    const error = new Error('Session not found or expired');
    error.code = 'SESSION_NOT_FOUND';
    error.status = 401;
    throw error;
  }

  const user = await findUserById(session.user_id);
  if (!user) {
    const error = new Error('User not found');
    error.code = 'USER_NOT_FOUND';
    error.status = 401;
    throw error;
  }

  const newRefreshToken = generateRefreshToken();
  const newCsrfToken = generateCsrfToken();
  const newRefreshTokenHash = hashToken(newRefreshToken);
  const newCsrfTokenHash = hashToken(newCsrfToken);

  const updatedSession = await rotateSessionTokens(
    session.id,
    newRefreshTokenHash,
    newCsrfTokenHash
  );

  if (!updatedSession) {
    const error = new Error('Failed to rotate session tokens');
    error.code = 'SESSION_ROTATION_FAILED';
    error.status = 500;
    throw error;
  }

  const accessToken = signAccessToken(user, session.id);
  const refreshTokenJwt = signRefreshToken({
    sub: user.id,
    sid: session.id,
  });

  return {
    user: {
      id: user.id,
      email: user.email,
      displayName: user.display_name,
      role: user.role,
      status: user.status,
    },
    accessToken,
    refreshToken: refreshTokenJwt,
    csrfToken: newCsrfToken,
  };
}
export async function registerUser({ email, password, displayName, userAgent, ipAddress }) {
  const { userExistsByEmail, createUser } = await import('../repositories/user.repository.js');

  if (!email || !password) {
    const err = new Error('Email and password are required');
    err.statusCode = 400;
    err.code = 'VALIDATION_ERROR';
    throw err;
  }

  if (password.length < 6) {
    const err = new Error('Password must be at least 6 characters');
    err.statusCode = 400;
    err.code = 'PASSWORD_TOO_SHORT';
    throw err;
  }

  const exists = await userExistsByEmail(email);
  if (exists) {
    const err = new Error('User with this email already exists');
    err.statusCode = 409;
    err.code = 'EMAIL_ALREADY_EXISTS';
    throw err;
  }

  const user = await createUser({ email, password, displayName });

  const refreshToken = generateRefreshToken();
  const csrfToken = generateCsrfToken();
  const refreshTokenHash = hashToken(refreshToken);
  const csrfTokenHash = hashToken(csrfToken);

  const session = await createAuthSession({
    userId: user.id,
    refreshTokenHash,
    csrfTokenHash,
    userAgent,
    ipAddress,
    expiresAt: buildSessionExpiresAt(),
  });

  const accessToken = signAccessToken(user, session.id);

  return {
    user: {
      id: user.id,
      email: user.email,
      displayName: user.display_name,
      role: user.role,
      status: user.status,
    },
    session: { id: session.id, expiresAt: session.expires_at },
    accessToken,
    refreshToken,
    csrfToken,
  };
}

export async function loginWithTelegram({ telegramData, userAgent, ipAddress }) {
  const crypto = await import("crypto");

  const { hash, ...fields } = telegramData;
  const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;

  if (!BOT_TOKEN) throw Object.assign(new Error("Telegram not configured"), { statusCode: 500, code: "CONFIG_ERROR" });

  // Verify auth_date freshness (max 1 day)
  const authDate = Number(fields.auth_date);
  if (!authDate || Date.now() / 1000 - authDate > 86400) {
    throw Object.assign(new Error("Telegram auth expired"), { statusCode: 401, code: "AUTH_EXPIRED" });
  }

  // Antifraud: require Telegram username (filters out throwaway / bot accounts)
  if (!fields.username || String(fields.username).trim().length < 3) {
    throw Object.assign(new Error("Telegram username required"), { statusCode: 400, code: "TELEGRAM_USERNAME_REQUIRED" });
  }

  // Verify HMAC-SHA256 signature
  const dataCheckString = Object.keys(fields)
    .sort()
    .filter(k => fields[k] !== undefined && fields[k] !== null)
    .map(k => `${k}=${fields[k]}`)
    .join("\n");

  const secretKey = crypto.createHash("sha256").update(BOT_TOKEN).digest();
  const hmac = crypto.createHmac("sha256", secretKey).update(dataCheckString).digest("hex");

  if (hmac !== hash) {
    throw Object.assign(new Error("Telegram signature invalid"), { statusCode: 401, code: "INVALID_SIGNATURE" });
  }

  const { findUserByTelegramId, createUserFromTelegram, countTelegramSignupsByIpLast24h, recordSignupAttempt } = await import("../repositories/user.repository.js");

  const telegramId = Number(fields.id);
  let user = await findUserByTelegramId(telegramId);

  if (!user) {
    // Antifraud: limit 3 new Telegram signups per IP per 24h
    const recentCount = await countTelegramSignupsByIpLast24h(ipAddress);
    if (recentCount >= 3) {
      throw Object.assign(new Error("Too many signups from this IP"), { statusCode: 429, code: "SIGNUP_RATE_LIMIT" });
    }
    const displayName = [fields.first_name, fields.last_name].filter(Boolean).join(" ") || fields.username || null;
    const email = `tg_${telegramId}@telegram.local`;
    user = await createUserFromTelegram({ telegramId, displayName, email });
    await recordSignupAttempt({ ipAddress, provider: 'telegram', telegramId, userAgent });
  }

  if (user.status !== "active") {
    throw Object.assign(new Error("Account is blocked"), { statusCode: 403, code: "ACCOUNT_BLOCKED" });
  }

  const refreshToken = generateRefreshToken();
  const csrfToken = generateCsrfToken();
  const refreshTokenHash = hashToken(refreshToken);
  const csrfTokenHash = hashToken(csrfToken);

  const session = await createAuthSession({
    userId: user.id,
    refreshTokenHash,
    csrfTokenHash,
    userAgent,
    ipAddress,
    expiresAt: buildSessionExpiresAt(),
  });

  const accessToken = signAccessToken(user, session.id);

  return {
    user: {
      id: user.id,
      email: user.email,
      displayName: user.display_name,
      role: user.role,
      status: user.status,
    },
    session: { id: session.id, expiresAt: session.expires_at },
    accessToken,
    refreshToken,
    csrfToken,
  };
}

export async function linkTelegramAccount({ userId, telegramData }) {
  const crypto = await import("crypto");
  const { hash, ...fields } = telegramData || {};
  const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
  if (!BOT_TOKEN) throw Object.assign(new Error("Telegram not configured"), { statusCode: 500, code: "CONFIG_ERROR" });

  const authDate = Number(fields.auth_date);
  if (!authDate || Date.now() / 1000 - authDate > 86400) {
    throw Object.assign(new Error("Telegram auth expired"), { statusCode: 401, code: "AUTH_EXPIRED" });
  }
  if (!fields.username || String(fields.username).trim().length < 3) {
    throw Object.assign(new Error("Telegram username required"), { statusCode: 400, code: "TELEGRAM_USERNAME_REQUIRED" });
  }
  const dataCheckString = Object.keys(fields)
    .sort()
    .filter(k => fields[k] !== undefined && fields[k] !== null)
    .map(k => `${k}=${fields[k]}`)
    .join("\n");
  const secretKey = crypto.createHash("sha256").update(BOT_TOKEN).digest();
  const hmac = crypto.createHmac("sha256", secretKey).update(dataCheckString).digest("hex");
  if (hmac !== hash) {
    throw Object.assign(new Error("Telegram signature invalid"), { statusCode: 401, code: "INVALID_SIGNATURE" });
  }

  const { findTelegramOwner, linkTelegramToUser } = await import("../repositories/user.repository.js");
  const telegramId = Number(fields.id);
  const owner = await findTelegramOwner(telegramId);
  if (owner && owner.id !== userId) {
    throw Object.assign(new Error("Telegram already linked to another account"), { statusCode: 409, code: "TELEGRAM_ALREADY_LINKED" });
  }
  await linkTelegramToUser(userId, telegramId);
  return { ok: true, telegramId };
}

export async function unlinkTelegramAccount(userId) {
  const { unlinkTelegramFromUser } = await import("../repositories/user.repository.js");
  await unlinkTelegramFromUser(userId);
  return { ok: true };
}


// ─── Bot deeplink login flow ─────────────────────────────────────────────────

export async function startTelegramLoginFlow({ intent, userId, ipAddress, userAgent }) {
  const crypto = await import("crypto");
  const { createTgLoginToken, countTgStartsByIpLastMinutes } = await import("../repositories/tg-login.repository.js");

  // Антифрод: 10 стартов с одного IP за 10 минут
  const recent = await countTgStartsByIpLastMinutes(ipAddress, 10);
  if (recent >= 10) {
    throw Object.assign(new Error("Too many login attempts. Try again later."),
      { statusCode: 429, code: "TG_START_RATE_LIMIT" });
  }

  // Telegram /start payload: до 64 base64url-символов
  const token = crypto.randomBytes(24).toString("base64url");
  const created = await createTgLoginToken({ token, intent, userId, ipAddress, userAgent });

  const botUsername = process.env.TELEGRAM_BOT_USERNAME || "One_Click_Auth_bot";
  const url = `https://t.me/${botUsername}?start=${token}`;

  return { token, url, expiresAt: created.expiresAt, ttlSec: created.ttlSec };
}

export async function confirmTelegramFromBot({ token, telegramUser }) {
  const { findTgLoginToken, markTgTokenConfirmed, markTgTokenError } = await import("../repositories/tg-login.repository.js");
  const {
    findUserByTelegramId, createUserFromTelegram,
    countTelegramSignupsByIpLast24h, recordSignupAttempt,
    findUserById, findTelegramOwner, linkTelegramToUser,
  } = await import("../repositories/user.repository.js");

  const row = await findTgLoginToken(token);
  if (!row) return { ok: false, code: "TOKEN_NOT_FOUND", message: "Ссылка не найдена или устарела." };
  if (row.status !== "pending") return { ok: false, code: "TOKEN_NOT_PENDING", message: "Эта ссылка уже использована. Запросите новую на сайте." };
  if (new Date(row.expires_at) < new Date()) {
    await markTgTokenError(token, "EXPIRED");
    return { ok: false, code: "EXPIRED", message: "Ссылка истекла. Запросите новую на сайте." };
  }

  // Антифрод: требуем username
  if (!telegramUser.username || String(telegramUser.username).trim().length < 3) {
    await markTgTokenError(token, "USERNAME_REQUIRED");
    return { ok: false, code: "USERNAME_REQUIRED", message: "У вашего Telegram-аккаунта должно быть @username." };
  }

  const telegramId = Number(telegramUser.id);

  if (row.intent === "link") {
    if (!row.user_id) {
      await markTgTokenError(token, "NO_USER");
      return { ok: false, code: "NO_USER", message: "Авторизация не найдена. Войдите на сайт и попробуйте снова." };
    }
    const owner = await findTelegramOwner(telegramId);
    if (owner && owner.id !== row.user_id) {
      await markTgTokenError(token, "TELEGRAM_ALREADY_LINKED");
      return { ok: false, code: "TELEGRAM_ALREADY_LINKED", message: "Этот Telegram уже привязан к другому аккаунту." };
    }
    await linkTelegramToUser(row.user_id, telegramId);
    await markTgTokenConfirmed(token, {
      telegramId,
      telegramUsername: telegramUser.username,
      telegramFirstName: telegramUser.first_name || null,
      telegramLastName: telegramUser.last_name || null,
      userId: row.user_id,
    });
    return { ok: true, message: "Готово! Telegram привязан. Возвращайтесь на сайт." };
  }

  // intent === "login"
  let user = await findUserByTelegramId(telegramId);
  if (!user) {
    const recent = await countTelegramSignupsByIpLast24h(row.ip_address);
    if (recent >= 3) {
      await markTgTokenError(token, "SIGNUP_RATE_LIMIT");
      return { ok: false, code: "SIGNUP_RATE_LIMIT", message: "Слишком много регистраций с этого IP. Попробуйте позже." };
    }
    const displayName = [telegramUser.first_name, telegramUser.last_name].filter(Boolean).join(" ") || telegramUser.username || null;
    const email = `tg_${telegramId}@telegram.local`;
    user = await createUserFromTelegram({ telegramId, displayName, email });
    await recordSignupAttempt({ ipAddress: row.ip_address, provider: "telegram", telegramId, userAgent: row.user_agent });
  }

  if (user.status !== "active") {
    await markTgTokenError(token, "ACCOUNT_BLOCKED");
    return { ok: false, code: "ACCOUNT_BLOCKED", message: "Аккаунт заблокирован." };
  }

  await markTgTokenConfirmed(token, {
    telegramId,
    telegramUsername: telegramUser.username,
    telegramFirstName: telegramUser.first_name || null,
    telegramLastName: telegramUser.last_name || null,
    userId: user.id,
  });
  return { ok: true, message: "Готово! Можете возвращаться на сайт — вы уже вошли." };
}

export async function pollTelegramLoginFlow({ token, userAgent, ipAddress }) {
  const { findTgLoginToken, consumeTgToken } = await import("../repositories/tg-login.repository.js");
  const { findUserById } = await import("../repositories/user.repository.js");

  const row = await findTgLoginToken(token);
  if (!row) return { status: "not_found" };

  if (row.status === "pending") {
    if (new Date(row.expires_at) < new Date()) return { status: "expired" };
    return { status: "pending" };
  }

  if (row.status === "error") {
    return { status: "error", code: row.error_code };
  }

  if (row.status === "consumed") {
    return { status: "consumed" };
  }

  if (row.status !== "confirmed") {
    return { status: row.status };
  }

  // confirmed → consume + maybe issue session
  const consumed = await consumeTgToken(token);
  if (!consumed) return { status: "consumed" };

  if (row.intent === "link") {
    return { status: "linked" };
  }

  // login: создаём сессию
  const user = await findUserById(row.user_id);
  if (!user) return { status: "error", code: "USER_NOT_FOUND" };

  const refreshToken = generateRefreshToken();
  const csrfToken = generateCsrfToken();
  const session = await createAuthSession({
    userId: user.id,
    refreshTokenHash: hashToken(refreshToken),
    csrfTokenHash: hashToken(csrfToken),
    userAgent, ipAddress,
    expiresAt: buildSessionExpiresAt(),
  });
  const accessToken = signAccessToken(user, session.id);

  return {
    status: "authenticated",
    user: {
      id: user.id, email: user.email, displayName: user.display_name,
      role: user.role, status: user.status,
    },
    accessToken, refreshToken, csrfToken,
    sessionExpiresAt: session.expires_at,
  };
}


// ─── Telegram Mini App (Web App) login by initData ───────────────────────────
// Спека: https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
// Алгоритм: secret = HMAC_SHA256("WebAppData", BOT_TOKEN);
//           expected = HMAC_SHA256(secret, data_check_string).hex();
// data_check_string = отсортированные по ключу пары "k=v", разделённые "\n", без поля "hash".
export async function loginWithTelegramInitData({ initData, userAgent, ipAddress }) {
  const crypto = await import("crypto");
  const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
  if (!BOT_TOKEN) {
    throw Object.assign(new Error("Telegram not configured"), { statusCode: 500, code: "CONFIG_ERROR" });
  }
  if (!initData || typeof initData !== "string") {
    throw Object.assign(new Error("initData is required"), { statusCode: 400, code: "INIT_DATA_REQUIRED" });
  }

  // Парсим как URLSearchParams
  const params = new URLSearchParams(initData);
  const hash = params.get("hash");
  if (!hash) {
    throw Object.assign(new Error("hash missing"), { statusCode: 401, code: "INVALID_INIT_DATA" });
  }
  params.delete("hash");

  const pairs = [];
  for (const [k, v] of params.entries()) pairs.push([k, v]);
  pairs.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
  const dataCheckString = pairs.map(([k, v]) => `${k}=${v}`).join("\n");

  const secretKey = crypto.createHmac("sha256", "WebAppData").update(BOT_TOKEN).digest();
  const expected = crypto.createHmac("sha256", secretKey).update(dataCheckString).digest("hex");

  // Константно-временное сравнение
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(hash, "hex");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    throw Object.assign(new Error("Telegram signature invalid"), { statusCode: 401, code: "INVALID_INIT_DATA" });
  }

  // Свежесть auth_date (24 часа, как в widget-flow)
  const authDate = Number(params.get("auth_date"));
  if (!authDate || Date.now() / 1000 - authDate > 86400) {
    throw Object.assign(new Error("Telegram auth expired"), { statusCode: 401, code: "AUTH_EXPIRED" });
  }

  // Парсим user
  let tgUser;
  try {
    tgUser = JSON.parse(params.get("user") || "null");
  } catch {
    tgUser = null;
  }
  if (!tgUser || !tgUser.id) {
    throw Object.assign(new Error("Telegram user missing"), { statusCode: 400, code: "TELEGRAM_USER_MISSING" });
  }

  // Антифрод: требуем username
  if (!tgUser.username || String(tgUser.username).trim().length < 3) {
    throw Object.assign(new Error("Telegram username required"), { statusCode: 400, code: "TELEGRAM_USERNAME_REQUIRED" });
  }

  const { findUserByTelegramId, createUserFromTelegram, countTelegramSignupsByIpLast24h, recordSignupAttempt } =
    await import("../repositories/user.repository.js");

  const telegramId = Number(tgUser.id);
  let user = await findUserByTelegramId(telegramId);

  if (!user) {
    // Антифрод: 3 регистрации с одного IP за 24 часа (общий счётчик с widget/bot-flow)
    const recentCount = await countTelegramSignupsByIpLast24h(ipAddress);
    if (recentCount >= 3) {
      throw Object.assign(new Error("Too many signups from this IP"), { statusCode: 429, code: "SIGNUP_RATE_LIMIT" });
    }
    const displayName =
      [tgUser.first_name, tgUser.last_name].filter(Boolean).join(" ") || tgUser.username || null;
    const email = `tg_${telegramId}@telegram.local`;
    user = await createUserFromTelegram({ telegramId, displayName, email });
    await recordSignupAttempt({ ipAddress, provider: "telegram_webapp", telegramId, userAgent });
  }

  if (user.status !== "active") {
    throw Object.assign(new Error("Account is blocked"), { statusCode: 403, code: "ACCOUNT_BLOCKED" });
  }

  const refreshToken = generateRefreshToken();
  const csrfToken = generateCsrfToken();
  const refreshTokenHash = hashToken(refreshToken);
  const csrfTokenHash = hashToken(csrfToken);

  const session = await createAuthSession({
    userId: user.id,
    refreshTokenHash,
    csrfTokenHash,
    userAgent,
    ipAddress,
    expiresAt: buildSessionExpiresAt(),
  });

  const accessToken = signAccessToken(user, session.id);

  return {
    user: {
      id: user.id,
      email: user.email,
      displayName: user.display_name,
      role: user.role,
      status: user.status,
    },
    session: { id: session.id, expiresAt: session.expires_at },
    accessToken,
    refreshToken,
    csrfToken,
  };
}
