import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import { compare } from 'bcryptjs'
import crypto from 'crypto'
import { prisma } from './prisma'
import { StaffRole, StaffStatus } from '@prisma/client'
import { totpService } from './services/totp-service'

// Pending 2FA sessions - stored in DB for multi-instance support
interface Pending2FASessionData {
  userId: string
  userType: 'customer' | 'staff'
  email: string
  name: string | null
  role?: StaffRole
  company?: string | null
  subscription?: {
    id: string
    plan: string
    tier: string
    status: string
  } | null
  expiresAt: number
}

/**
 * Get a pending 2FA session by token (from DB)
 */
export async function getPending2FASession(token: string): Promise<Pending2FASessionData | null> {
  const session = await prisma.pending2FASession.findUnique({
    where: { token },
  })
  if (!session) return null
  if (session.expiresAt < new Date()) {
    await prisma.pending2FASession.delete({ where: { token } }).catch(() => {})
    return null
  }
  return {
    userId: session.userId,
    userType: session.userType as 'customer' | 'staff',
    email: session.email,
    name: session.name,
    role: session.role as StaffRole | undefined,
    company: session.company,
    subscription: session.subscription as Pending2FASessionData['subscription'],
    expiresAt: session.expiresAt.getTime(),
  }
}

/**
 * Store a pending 2FA session in DB
 */
async function storePending2FASession(token: string, data: Pending2FASessionData): Promise<void> {
  await prisma.pending2FASession.create({
    data: {
      token,
      userId: data.userId,
      userType: data.userType,
      email: data.email,
      name: data.name,
      role: data.role ?? null,
      company: data.company ?? null,
      subscription: data.subscription ?? undefined,
      expiresAt: new Date(data.expiresAt),
    },
  })
}

/**
 * Clear a pending 2FA session after successful verification
 */
export async function clearPending2FASession(token: string): Promise<void> {
  await prisma.pending2FASession.delete({ where: { token } }).catch(() => {})
}

export const { handlers, auth, signIn, signOut } = NextAuth({
  session: {
    strategy: 'jwt',
    maxAge: 7 * 24 * 60 * 60, // 7 days
    updateAge: 24 * 60 * 60, // Refresh token every 24 hours
  },
  pages: {
    signIn: '/login',
    error: '/login',
  },
  providers: [
    Credentials({
      name: 'credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
        userType: { label: 'User Type', type: 'text' }, // 'customer' or 'staff'
        pendingToken: { label: 'Pending Token', type: 'text' }, // For 2FA verification
        twoFactorToken: { label: '2FA Token', type: 'text' }, // TOTP code or backup code
      },
      async authorize(credentials) {
        // Handle 2FA verification step
        if (credentials?.pendingToken && credentials?.twoFactorToken) {
          const pendingToken = credentials.pendingToken as string
          const twoFactorToken = credentials.twoFactorToken as string

          const pending = await getPending2FASession(pendingToken)
          if (!pending) {
            throw new Error('Session expired. Please log in again.')
          }

          // Get 2FA secret and backup codes
          let encryptedSecret: string | null = null
          let backupCodesEnc: string | null = null

          if (pending.userType === 'staff') {
            const staff = await prisma.staff.findUnique({
              where: { id: pending.userId },
              select: { twoFactorSecretEnc: true, backupCodesEnc: true },
            })
            encryptedSecret = staff?.twoFactorSecretEnc ?? null
            backupCodesEnc = staff?.backupCodesEnc ?? null
          } else {
            const user = await prisma.user.findUnique({
              where: { id: pending.userId },
              select: { twoFactorSecretEnc: true, backupCodesEnc: true },
            })
            encryptedSecret = user?.twoFactorSecretEnc ?? null
            backupCodesEnc = user?.backupCodesEnc ?? null
          }

          if (!encryptedSecret) {
            throw new Error('2FA configuration error')
          }

          // Try TOTP verification first
          const secret = totpService.decryptSecret(encryptedSecret)
          let isValid = totpService.verifyToken(twoFactorToken, secret)

          // If TOTP fails, try backup code
          if (!isValid && backupCodesEnc) {
            const backupResult = await totpService.verifyBackupCode(twoFactorToken, backupCodesEnc)
            if (backupResult.valid && backupResult.updatedHashedCodesJson) {
              isValid = true
              // Update remaining backup codes
              if (pending.userType === 'staff') {
                await prisma.staff.update({
                  where: { id: pending.userId },
                  data: { backupCodesEnc: backupResult.updatedHashedCodesJson },
                })
              } else {
                await prisma.user.update({
                  where: { id: pending.userId },
                  data: { backupCodesEnc: backupResult.updatedHashedCodesJson },
                })
              }
            }
          }

          if (!isValid) {
            throw new Error('Invalid 2FA code')
          }

          // Clear pending session
          await clearPending2FASession(pendingToken)

          // Return the user data from the pending session
          if (pending.userType === 'staff') {
            return {
              id: pending.userId,
              email: pending.email,
              name: pending.name,
              userType: 'staff' as const,
              role: pending.role,
            }
          } else {
            return {
              id: pending.userId,
              email: pending.email,
              name: pending.name,
              userType: 'customer' as const,
              company: pending.company,
              subscription: pending.subscription,
            }
          }
        }

        // Regular login flow
        if (!credentials?.email || !credentials?.password || !credentials?.userType) {
          throw new Error('Missing credentials')
        }

        const email = credentials.email as string
        const password = credentials.password as string
        const userType = credentials.userType as 'customer' | 'staff'

        if (userType === 'customer') {
          const user = await prisma.user.findUnique({
            where: { email },
            include: {
              subscriptions: {
                where: { status: { not: 'CANCELED' } },
                orderBy: { createdAt: 'desc' },
                take: 1,
              },
            },
          })

          if (!user) {
            throw new Error('Invalid email or password')
          }

          if (user.status === 'SUSPENDED') {
            throw new Error('Account suspended')
          }

          const isValidPassword = await compare(password, user.passwordHash)
          if (!isValidPassword) {
            throw new Error('Invalid email or password')
          }

          // Check if 2FA is enabled
          if (user.twoFactorEnabled) {
            // Create pending 2FA session (stored in DB)
            const pendingToken = crypto.randomBytes(32).toString('hex')
            const subscription = user.subscriptions[0]
            await storePending2FASession(pendingToken, {
              userId: user.id,
              userType: 'customer',
              email: user.email,
              name: user.name,
              company: user.company,
              subscription: subscription ? {
                id: subscription.id,
                plan: subscription.plan,
                tier: subscription.tier,
                status: subscription.status,
              } : null,
              expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
            })
            throw new Error(`2FA_REQUIRED:${pendingToken}`)
          }

          return {
            id: user.id,
            email: user.email,
            name: user.name,
            userType: 'customer' as const,
            company: user.company,
            subscription: user.subscriptions[0] || null,
          }
        } else if (userType === 'staff') {
          const staff = await prisma.staff.findUnique({
            where: { email },
          })

          if (!staff) {
            throw new Error('Invalid email or password')
          }

          // Check if staff is suspended
          if (staff.status === StaffStatus.SUSPENDED) {
            throw new Error('Account suspended')
          }

          const isValidPassword = await compare(password, staff.passwordHash)
          if (!isValidPassword) {
            throw new Error('Invalid email or password')
          }

          // Check if 2FA is enabled
          if (staff.twoFactorEnabled) {
            // Create pending 2FA session (stored in DB)
            const pendingToken = crypto.randomBytes(32).toString('hex')
            await storePending2FASession(pendingToken, {
              userId: staff.id,
              userType: 'staff',
              email: staff.email,
              name: staff.name,
              role: staff.role,
              expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
            })
            throw new Error(`2FA_REQUIRED:${pendingToken}`)
          }

          return {
            id: staff.id,
            email: staff.email,
            name: staff.name,
            userType: 'staff' as const,
            role: staff.role,
          }
        }

        throw new Error('Invalid user type')
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id
        token.userType = user.userType
        if (user.userType === 'staff') {
          token.role = user.role
        }
        if (user.userType === 'customer') {
          token.company = user.company
          token.subscription = user.subscription
        }
      }
      return token
    },
    async session({ session, token }) {
      if (token) {
        session.user.id = token.id as string
        session.user.userType = token.userType as 'customer' | 'staff'
        if (token.userType === 'staff') {
          session.user.role = token.role as StaffRole
        }
        if (token.userType === 'customer') {
          session.user.company = token.company as string | undefined
          session.user.subscription = token.subscription as {
            id: string
            plan: string
            tier: string
            status: string
          } | null
        }
      }
      return session
    },
    async authorized({ auth, request }) {
      const isLoggedIn = !!auth?.user
      const isAdminRoute = request.nextUrl.pathname.startsWith('/admin')
      const isApiAdminRoute = request.nextUrl.pathname.startsWith('/api/v1/admin')

      if (isAdminRoute || isApiAdminRoute) {
        if (!isLoggedIn) return false
        return auth.user.userType === 'staff'
      }

      return true
    },
  },
})
