import { SignJWT, jwtVerify } from "jose"
import bcrypt from "bcryptjs"
import { query } from "@/lib/db"
import { cookies } from "next/headers"

const JWT_SECRET = new TextEncoder().encode(
  process.env.JWT_SECRET || "fallback-dev-secret-do-not-use-in-production"
)

const SESSION_COOKIE = "session"
const MAX_FAILED_ATTEMPTS = 5
const LOCKOUT_DURATION_MINUTES = 15

export interface SessionUser {
  id: string
  username: string
  email: string
  firstName: string
  lastName: string
  role: string
  avatar: string
}

export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, 12)
}

export async function comparePassword(
  password: string,
  hash: string
): Promise<boolean> {
  return bcrypt.compare(password, hash)
}

export async function signToken(payload: { userId: string; role: string }) {
  return new SignJWT(payload)
    .setProtectedHeader({ alg: "HS256" })
    .setExpirationTime("24h")
    .setIssuedAt()
    .sign(JWT_SECRET)
}

export async function verifyToken(token: string) {
  try {
    const { payload } = await jwtVerify(token, JWT_SECRET)
    return payload as { userId: string; role: string }
  } catch {
    return null
  }
}

export async function getUserByEmail(email: string) {
  const result = await query(
    `SELECT u.id, u.username, u.email, u.password_hash, u.first_name, u.last_name,
            u.is_active, u.is_locked, u.failed_login_attempts, u.locked_until,
            u.password_change_required,
            r.name AS role_name
     FROM users u
     JOIN user_roles ur ON ur.user_id = u.id
     JOIN roles r ON r.id = ur.role_id
     WHERE u.email = $1 AND u.deleted_at IS NULL
     LIMIT 1`,
    [email.toLowerCase().trim()]
  )
  return result.rows[0] || null
}

export async function getUserByUsername(username: string) {
  const result = await query(
    `SELECT u.id, u.username, u.email, u.password_hash, u.first_name, u.last_name,
            u.is_active, u.is_locked, u.failed_login_attempts, u.locked_until,
            u.password_change_required,
            r.name AS role_name
     FROM users u
     JOIN user_roles ur ON ur.user_id = u.id
     JOIN roles r ON r.id = ur.role_id
     WHERE u.username = $1 AND u.deleted_at IS NULL
     LIMIT 1`,
    [username.toLowerCase().trim()]
  )
  return result.rows[0] || null
}

export async function getUserById(id: string) {
  const result = await query(
    `SELECT u.id, u.username, u.email, u.first_name, u.last_name,
            u.is_active,
            r.name AS role_name
     FROM users u
     JOIN user_roles ur ON ur.user_id = u.id
     JOIN roles r ON r.id = ur.role_id
     WHERE u.id = $1 AND u.deleted_at IS NULL
     LIMIT 1`,
    [id]
  )
  return result.rows[0] || null
}

export async function recordLoginAttempt(
  userId: string | null,
  usernameAttempted: string,
  ipAddress: string,
  userAgent: string | null,
  wasSuccessful: boolean,
  failureReason?: string
) {
  await query(
    `INSERT INTO login_attempts (user_id, username_attempted, ip_address, user_agent, was_successful, failure_reason)
     VALUES ($1, $2, $3, $4, $5, $6)`,
    [userId, usernameAttempted, ipAddress, userAgent, wasSuccessful, failureReason || null]
  )
}

export async function incrementFailedAttempts(userId: string) {
  await query(
    `UPDATE users
     SET failed_login_attempts = failed_login_attempts + 1,
         locked_until = CASE
           WHEN failed_login_attempts + 1 >= $2
            THEN NOW() + (INTERVAL '1 minute' * $3)
           ELSE locked_until
         END
     WHERE id = $1`,
    [userId, MAX_FAILED_ATTEMPTS, LOCKOUT_DURATION_MINUTES]
  )
}

export async function resetFailedAttempts(userId: string) {
  await query(
    `UPDATE users
     SET failed_login_attempts = 0,
         locked_until = NULL,
         last_login_at = NOW()
     WHERE id = $1`,
    [userId]
  )
}

export async function isAccountLocked(user: {
  is_locked: boolean
  locked_until: Date | null
}): Promise<{ locked: boolean; reason?: string }> {
  if (user.is_locked) {
    return { locked: true, reason: "Account has been locked by an administrator." }
  }
  if (user.locked_until && new Date(user.locked_until) > new Date()) {
    const minutes = Math.ceil(
      (new Date(user.locked_until).getTime() - Date.now()) / 60000
    )
    return {
      locked: true,
      reason: `Account is temporarily locked due to too many failed attempts. Try again in ${minutes} minute(s).`,
    }
  }
  return { locked: false }
}

export function mapDbUserToSessionUser(dbUser: Record<string, unknown>): SessionUser {
  const roleName = (dbUser.role_name as string).toLowerCase()

  return {
    id: dbUser.id as string,
    username: dbUser.username as string,
    email: dbUser.email as string,
    firstName: dbUser.first_name as string,
    lastName: dbUser.last_name as string,
    role: roleName,
    avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(
      `${dbUser.first_name}+${dbUser.last_name}`
    )}&background=1d4ed8&color=fff&size=128`,
  }
}

export async function createSession(userId: string, role: string) {
  const token = await signToken({ userId, role })

  const cookieStore = await cookies()
  cookieStore.set(SESSION_COOKIE, token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    path: "/",
    maxAge: 60 * 60 * 24, // 24 hours
  })

  return token
}

export async function destroySession() {
  const cookieStore = await cookies()
  cookieStore.set(SESSION_COOKIE, "", {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    path: "/",
    maxAge: 0,
  })
}

export async function getSessionUser(): Promise<SessionUser | null> {
  try {
    const cookieStore = await cookies()
    const token = cookieStore.get(SESSION_COOKIE)?.value
    if (!token) return null

    const payload = await verifyToken(token)
    if (!payload) return null

    const dbUser = await getUserById(payload.userId)
    if (!dbUser) return null

    return mapDbUserToSessionUser(dbUser)
  } catch {
    return null
  }
}
