// services/auth.service.js
const bcrypt = require("bcryptjs")
const jwt = require("jsonwebtoken")
const crypto = require("crypto")

const authDb = require("../db/auth.db")
const refreshTokenDb = require("../db/refreshToken.db")

function httpError(statusCode, message) {
  const err = new Error(message)
  err.statusCode = statusCode
  return err
}

// Access token: kısa ömür
function signAccessToken(user) {
  const jti = crypto.randomUUID()
  const payload = {
    sub: String(user.id),
    role: user.role, // USER|MOD|ADMIN
    jti,
  }

  const expiresIn = process.env.ACCESS_TOKEN_EXPIRES_IN || "15m"
  const token = jwt.sign(payload, process.env.JWT_ACCESS_SECRET, { expiresIn })
  return { token, jti }
}

// Refresh token: opaque (JWT değil) + DB’de hash
function generateRefreshToken() {
  // 64 byte -> url-safe base64
  return crypto.randomBytes(64).toString("base64url")
}

function hashToken(token) {
  return crypto.createHash("sha256").update(token).digest("hex")
}

function refreshExpiresAt() {
  const days = Number(process.env.REFRESH_TOKEN_DAYS || 30)
  return new Date(Date.now() + days * 24 * 60 * 60 * 1000)
}

function mapUserPublic(user) {
  return {
    id: user.id,
    username: user.username,
    email: user.email,
    avatarUrl: user.avatarUrl ?? null,
    role: user.role,
  }
}

async function login({ email, password, meta = {} }) {
  const user = await authDb.findUserByEmail(email)
  if (!user) throw httpError(400, "Kullanıcı bulunamadı.")

  const isMatch = await bcrypt.compare(password, user.passwordHash)
  if (!isMatch) throw httpError(401, "Şifre hatalı.")

  const { token: accessToken } = signAccessToken(user)

  const refreshToken = generateRefreshToken()
  const tokenHash = hashToken(refreshToken)
  const familyId = crypto.randomUUID()
  const jti = crypto.randomUUID()

  await refreshTokenDb.createRefreshToken(user.id, {
    tokenHash,
    familyId,
    jti,
    expiresAt: refreshExpiresAt(),
    createdByIp: meta.ip ?? null,
    userAgent: meta.userAgent ?? null,
  })

  return {
    accessToken,
    refreshToken,
    user: mapUserPublic(user),
  }
}

async function register({ username, email, password, meta = {} }) {
  const existingUser = await authDb.findUserByEmail(email)
  if (existingUser) throw httpError(400, "Bu e-posta zaten kayıtlı.")

  const passwordHash = await bcrypt.hash(password, 10)
  const user = await authDb.createUser({ username, email, passwordHash })

  const { token: accessToken } = signAccessToken(user)

  const refreshToken = generateRefreshToken()
  const tokenHash = hashToken(refreshToken)
  const familyId = crypto.randomUUID()
  const jti = crypto.randomUUID()

  await refreshTokenDb.createRefreshToken(user.id, {
    tokenHash,
    familyId,
    jti,
    expiresAt: refreshExpiresAt(),
    createdByIp: meta.ip ?? null,
    userAgent: meta.userAgent ?? null,
  })

  return {
    accessToken,
    refreshToken,
    user: mapUserPublic(user),
  }
}

// Refresh: rotate + reuse tespiti
async function refresh({ refreshToken, meta = {} }) {
  if (!refreshToken) throw httpError(401, "Refresh token yok")

  const tokenHash = hashToken(refreshToken)
  const existing = await refreshTokenDb.findRefreshTokenByHash(tokenHash, {
    include: { user: true },
  })

  if (!existing) throw httpError(401, "Refresh token geçersiz")

  // süresi geçmiş
  if (existing.expiresAt && existing.expiresAt.getTime() < Date.now()) {
    await refreshTokenDb.revokeRefreshTokenById(existing.id)
    throw httpError(401, "Refresh token süresi dolmuş")
  }

  // reuse tespiti: revoke edilmiş token tekrar gelirse -> tüm aileyi kapat
  if (existing.revokedAt) {
    await refreshTokenDb.revokeRefreshTokenFamily(existing.familyId)
    throw httpError(401, "Refresh token reuse tespit edildi")
  }

  const user = existing.user
  const { token: accessToken } = signAccessToken(user)

  const newRefreshToken = generateRefreshToken()
  const newTokenHash = hashToken(newRefreshToken)
  const newJti = crypto.randomUUID()

  await refreshTokenDb.rotateRefreshToken({
    oldId: existing.id,
    newToken: {
      userId: user.id,
      tokenHash: newTokenHash,
      familyId: existing.familyId, // aynı aile
      jti: newJti,
      expiresAt: refreshExpiresAt(),
    },
    meta: { ip: meta.ip ?? null, userAgent: meta.userAgent ?? null },
  })

  return {
    accessToken,
    refreshToken: newRefreshToken,
    user: mapUserPublic(user),
  }
}

async function logout({ refreshToken }) {
  if (!refreshToken) return
  const tokenHash = hashToken(refreshToken)

  // token yoksa sessiz geçmek genelde daha iyi (idempotent logout)
  try {
    await refreshTokenDb.revokeRefreshTokenByHash(tokenHash)
  } catch (_) {}
}

async function getMe(userId) {
  const user = await authDb.findUserById(Number(userId), {
    select: { id: true, username: true, email: true, avatarUrl: true, role: true },
  })
  if (!user) throw httpError(404, "Kullanıcı bulunamadı")
  return user
}

module.exports = {
  login,
  register,
  refresh,
  logout,
  getMe,
}
