import { compare } from "bcryptjs"
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import { eq } from "drizzle-orm"
import { resolvePermissions } from "@/shared/lib/permissions"
import { isRole } from "@/shared/types/permissions"
import { logLoginEvent } from "@/shared/lib/login-logger"
import {
  PASSWORD_RULES,
  isAccountLocked,
} from "@/shared/lib/password-policy"
import { RATE_LIMIT_RULES, rateLimit, rateLimitKey, resetRateLimit } from "@/shared/lib/rate-limit"
import { normalizeBcryptHash } from "@/shared/lib/bcrypt-utils"
import { resolveClientIp } from "@/shared/lib/http-utils"
import {
  getOrCreatePasswordSecurity,
  recordFailedLogin,
  resetFailedLogin,
} from "@/shared/lib/password-security-service"
import { normalizeRole, resolvePrimaryRole } from "@/shared/lib/role-utils"

export const { handlers, auth, signIn, signOut } = NextAuth({
  trustHost: true,
  secret: process.env.NEXTAUTH_SECRET,
  session: { strategy: "jwt" },
  pages: { signIn: "/login" },
  providers: [
    Credentials({
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      authorize: async (credentials) => {
        const email = String(credentials?.email ?? "").trim().toLowerCase()
        const password = String(credentials?.password ?? "")
        if (!email || !password) return null

        // Rate limit by IP + email to slow brute-force attempts
        const clientIp = await resolveClientIp()
        const loginLimitKey = rateLimitKey("login", `${clientIp}:${email}`)
        const limit = rateLimit({
          key: loginLimitKey,
          ...RATE_LIMIT_RULES.LOGIN,
        })
        if (!limit.success) {
          await logLoginEvent({
            userEmail: email,
            action: "signin",
            status: "failure",
            errorMessage: "Rate limit exceeded",
          })
          return null
        }

        const [{ db }, { users, roles, usersToRoles, passwordSecurity }] = await Promise.all([
          import("@/shared/db"),
          import("@/shared/db/schema"),
        ])

        const user = await db.query.users.findFirst({
          where: eq(users.email, email),
        })
        if (!user) return null

        // Account lockout check
        const security = await getOrCreatePasswordSecurity(db, passwordSecurity, user.id)
        const lastFailedAt = security.lockedUntil
          ? new Date(security.lockedUntil.getTime() - PASSWORD_RULES.lockoutDurationMinutes * 60 * 1000)
          : null
        if (isAccountLocked(security.failedLoginAttempts, lastFailedAt)) {
          await logLoginEvent({
            userId: user.id,
            userEmail: email,
            action: "signin",
            status: "failure",
            errorMessage: "Account locked",
          })
          return null
        }

        const storedPassword = user.password ?? null
        if (!storedPassword) return null
        const normalizedPassword = normalizeBcryptHash(storedPassword)
        if (!normalizedPassword.startsWith("$2")) return null
        const ok = await compare(password, normalizedPassword)

        if (!ok) {
          await recordFailedLogin(db, passwordSecurity, user.id)
          await logLoginEvent({
            userId: user.id,
            userEmail: email,
            action: "signin",
            status: "failure",
            errorMessage: "Invalid credentials",
          })
          return null
        }

        // Successful login: reset counters and rate limit
        await resetFailedLogin(db, passwordSecurity, user.id)
        resetRateLimit(loginLimitKey)

        const roleRows = await db
          .select({ name: roles.name })
          .from(usersToRoles)
          .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
          .where(eq(usersToRoles.userId, user.id))

        const roleNames = roleRows.map((r) => r.name)
        const resolvedRole = resolvePrimaryRole(roleNames)

        return {
          id: user.id,
          name: user.name ?? undefined,
          email: user.email,
          role: resolvedRole,
          roles: roleNames,
        }
      },
    }),
  ],
  callbacks: {
    jwt: async ({ token, user }) => {
      if (user) {
        const u = user as { id: string; role?: string; roles?: string[]; name?: string }
        token.id = u.id
        token.role = normalizeRole(u.role)
        token.name = u.name ?? undefined
        // Store all roles (not just primary) and resolved permissions
        const allRoles = (u.roles ?? [u.role ?? "student"]).filter(isRole)
        token.roles = allRoles
        token.permissions = resolvePermissions(allRoles)
      }

      // Refresh roles/permissions from DB on each JWT refresh
      const userId = String(token.id ?? "").trim()
      if (userId) {
        const [{ eq }, { db }, { roles, users, usersToRoles }] = await Promise.all([
          import("drizzle-orm"),
          import("@/shared/db"),
          import("@/shared/db/schema"),
        ])

        const [fresh, roleRows] = await Promise.all([
          db.query.users.findFirst({
            where: eq(users.id, userId),
            columns: { name: true },
          }),
          db
            .select({ name: roles.name })
            .from(usersToRoles)
            .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
            .where(eq(usersToRoles.userId, userId)),
        ])

        if (fresh) {
          const allRoles = roleRows.map((r) => r.name).filter(isRole)
          token.role = resolvePrimaryRole(allRoles)
          token.name = fresh.name ?? token.name
          token.roles = allRoles
          token.permissions = resolvePermissions(allRoles)
        }
      }

      return token
    },
    session: async ({ session, token }) => {
      if (session.user) {
        session.user.id = String(token.id ?? "")
        session.user.role = normalizeRole(token.role)
        session.user.roles = (token.roles ?? []).filter(isRole)
        session.user.permissions = (token.permissions ?? []) as typeof token.permissions
        if (typeof token.name === "string") {
          session.user.name = token.name
        }
      }
      return session
    },
  },
  events: {
    async signIn({ user }) {
      await logLoginEvent({
        userId: user.id,
        userEmail: user.email ?? "",
        action: "signin",
        status: "success",
      })
    },
    async signOut(message) {
      // NextAuth v5 signOut event receives the session/token info
      const userId =
        (message as { userId?: string })?.userId ??
        (message as { token?: { id?: string } })?.token?.id ??
        ""
      const userEmail =
        (message as { token?: { email?: string } })?.token?.email ??
        (message as { session?: { user?: { email?: string } } })?.session?.user?.email ??
        ""
      if (userEmail) {
        await logLoginEvent({
          userId: userId || undefined,
          userEmail,
          action: "signout",
          status: "success",
        })
      }
    },
  },
})
