import NextAuth, { type DefaultSession } from "next-auth"
import Credentials from "next-auth/providers/credentials"
import { ZodError } from "zod"

import { UserRole } from "@/generated/prisma/client"
import { SIGN_IN_URL, TOKEN_EXPIRATION_SECONDS } from "@/lib/constants"
import { signInSchema } from "@/lib/schemas/auth.schemas"
import { verifyPassword } from "@/lib/security"
import { getUserByUsername } from "@/services/user.service"

declare module "next-auth" {
  // eslint-disable-next-line no-unused-vars
  interface Session {
    user: {
      id: string
      role: UserRole
    } & DefaultSession["user"]
  }

  // eslint-disable-next-line no-unused-vars
  interface User {
    role: UserRole
  }
}

export const { handlers, signIn, signOut, auth } = NextAuth({
  session: {
    strategy: "jwt",
    maxAge: TOKEN_EXPIRATION_SECONDS,
  },
  providers: [
    Credentials({
      credentials: {
        username: {},
        password: {},
      },
      authorize: async (credentials) => {
        try {
          const { data, success } = signInSchema.safeParse(credentials)

          if (!success) throw new Error("Invalid username or password")

          const user = await getUserByUsername(data.username)

          if (!user) {
            throw new Error("Invalid username or password")
          }

          if (!(await verifyPassword(data.password, user.password)))
            throw new Error("Invalid username or password")

          return {
            ...user,
            role: user.role,
          }
        } catch (error) {
          if (error instanceof ZodError) {
            return null
          }
          throw new Error("Invalid username or password")
        }
      },
    }),
  ],
  pages: {
    signIn: SIGN_IN_URL,
  },
  callbacks: {
    async session({ session, token }) {
      if (!token) return session
      session.user.id = token.sub as string
      session.user.name = token.name as string
      session.user.email = token.email as string
      session.user.role = token.role as UserRole
      return session
    },
    async jwt({ token, user }) {
      if (user) {
        token.sub = user.id
        token.name = user.name
        token.email = user.email
        token.role = user.role
      }
      return token
    },
    authorized: async ({ auth }) => {
      return !!auth
    },
  },
})
