import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import bcrypt from "bcryptjs"
import { db } from "./db"
import type { Prisma } from "@prisma/client"

type DbUserWithRoles = Prisma.UserGetPayload<{
  include: { roles: { include: { role: true } } }
}>

function toStringOrEmpty(value: unknown): string {
  return typeof value === "string" ? value : ""
}

function toStringArray(value: unknown): string[] {
  return Array.isArray(value) ? value.filter((v): v is string => typeof v === "string") : []
}

export const { handlers, auth, signIn, signOut } = NextAuth({
  trustHost: true,
  session: { strategy: "jwt" },
  pages: {
    signIn: "/login",
  },
  callbacks: {
    async redirect({ url, baseUrl }) {
      // Always trust absolute URLs that match our real domain
      if (url.startsWith(baseUrl)) return url
      // Allow relative URLs (e.g. "/login")
      if (url.startsWith("/")) return `${baseUrl}${url}`
      // Fallback to our own domain instead of whatever Auth.js inferred
      return baseUrl
    },
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id
        token.roleIds = user.roleIds
        token.roles = user.roles
      }

      token.roleIds = Array.isArray(token.roleIds) ? token.roleIds : []
      token.roles = Array.isArray(token.roles) ? token.roles : []

      return token
    },
    async session({ session, token }) {
      session.user.id = toStringOrEmpty(token.id)
      session.user.roleIds = toStringArray(token.roleIds)
      session.user.roles = toStringArray(token.roles)
      return session
    },
  },
  providers: [
    Credentials({
      name: "credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) return null

        const user = (await db.user.findUnique({
          where: { email: credentials.email as string },
          include: {
            roles: {
              include: {
                role: true,
              },
            },
          },
        })) as DbUserWithRoles | null

        if (!user || !user.isActive) return null

        const isValid = await bcrypt.compare(
          credentials.password as string,
          user.password
        )

        if (!isValid) return null

        const roleIds = user.roles.map((r) => r.roleId)
        const roles = user.roles.map((r) => r.role.name)

        return {
          id: user.id,
          name: user.name,
          email: user.email,
          roleIds,
          roles,
        }
      },
    }),
  ],
})