import NextAuth from 'next-auth'
import EmailProvider from 'next-auth/providers/email'
import CredentialsProvider from 'next-auth/providers/credentials'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from './prisma'
import { sendMagicLinkEmail } from './email'
import { verifyPassword } from './password'
import type { UserRole } from '@prisma/client'
import { authConfig } from './auth.config'

// Failed login attempt tracking (in-memory)
const failedAttempts = new Map<string, { count: number; lockedUntil: number }>()
const MAX_LOGIN_ATTEMPTS = 5
const LOCKOUT_DURATION_MS = 15 * 60 * 1000 // 15 minutes

export const { handlers, auth, signIn, signOut } = NextAuth({
  ...authConfig,
  adapter: PrismaAdapter(prisma),
  providers: [
    // Email provider for magic links (used for first login and password reset)
    EmailProvider({
      // Server config required by NextAuth validation but not used —
      // sendVerificationRequest below fully overrides email sending via getTransporter()
      server: {
        host: process.env.SMTP_HOST || 'localhost',
        port: Number(process.env.SMTP_PORT || 587),
        auth: {
          user: process.env.SMTP_USER || '',
          pass: process.env.SMTP_PASS || '',
        },
      },
      from: process.env.EMAIL_FROM || 'MOPC Platform <noreply@monaco-opc.com>',
      maxAge: parseInt(process.env.MAGIC_LINK_EXPIRY || '900'), // 15 minutes
      sendVerificationRequest: async ({ identifier: email, url }) => {
        await sendMagicLinkEmail(email, url)
      },
    }),
    // Credentials provider for email/password login and invite token auth
    CredentialsProvider({
      name: 'credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
        inviteToken: { label: 'Invite Token', type: 'text' },
      },
      async authorize(credentials) {
        // Handle invite token authentication
        if (credentials?.inviteToken) {
          const token = credentials.inviteToken as string
          const user = await prisma.user.findUnique({
            where: { inviteToken: token },
            select: {
              id: true,
              email: true,
              name: true,
              role: true,
              status: true,
              inviteTokenExpiresAt: true,
            },
          })

          if (!user || user.status !== 'INVITED') {
            return null
          }

          if (user.inviteTokenExpiresAt && user.inviteTokenExpiresAt < new Date()) {
            return null
          }

          // Clear token, activate user, mark as needing password
          await prisma.user.update({
            where: { id: user.id },
            data: {
              inviteToken: null,
              inviteTokenExpiresAt: null,
              status: 'ACTIVE',
              mustSetPassword: true,
              lastLoginAt: new Date(),
            },
          })

          // Log invitation accepted
          await prisma.auditLog.create({
            data: {
              userId: user.id,
              action: 'INVITATION_ACCEPTED',
              entityType: 'User',
              entityId: user.id,
              detailsJson: { email: user.email, role: user.role },
            },
          }).catch(() => {})

          return {
            id: user.id,
            email: user.email,
            name: user.name,
            role: user.role,
            mustSetPassword: true,
          }
        }

        if (!credentials?.email || !credentials?.password) {
          return null
        }

        const email = (credentials.email as string).toLowerCase()
        const password = credentials.password as string

        // Check if account is temporarily locked
        const attempts = failedAttempts.get(email)
        if (attempts && Date.now() < attempts.lockedUntil) {
          throw new Error('Account temporarily locked due to too many failed attempts. Try again later.')
        }

        // Find user by email
        const user = await prisma.user.findUnique({
          where: { email },
          select: {
            id: true,
            email: true,
            name: true,
            role: true,
            status: true,
            passwordHash: true,
            mustSetPassword: true,
          },
        })

        if (!user || user.status === 'SUSPENDED' || !user.passwordHash) {
          // Track failed attempt (don't reveal whether user exists)
          const current = failedAttempts.get(email) || { count: 0, lockedUntil: 0 }
          current.count++
          if (current.count >= MAX_LOGIN_ATTEMPTS) {
            current.lockedUntil = Date.now() + LOCKOUT_DURATION_MS
            current.count = 0
          }
          failedAttempts.set(email, current)

          // Log failed login
          await prisma.auditLog.create({
            data: {
              userId: null,
              action: 'LOGIN_FAILED',
              entityType: 'User',
              detailsJson: { email, reason: !user ? 'user_not_found' : user.status === 'SUSPENDED' ? 'suspended' : 'no_password' },
            },
          }).catch(() => {})

          return null
        }

        // Verify password
        const isValid = await verifyPassword(password, user.passwordHash)
        if (!isValid) {
          // Track failed attempt
          const current = failedAttempts.get(email) || { count: 0, lockedUntil: 0 }
          current.count++
          if (current.count >= MAX_LOGIN_ATTEMPTS) {
            current.lockedUntil = Date.now() + LOCKOUT_DURATION_MS
            current.count = 0
          }
          failedAttempts.set(email, current)

          // Log failed login
          await prisma.auditLog.create({
            data: {
              userId: user.id,
              action: 'LOGIN_FAILED',
              entityType: 'User',
              entityId: user.id,
              detailsJson: { email, reason: 'invalid_password' },
            },
          }).catch(() => {})

          return null
        }

        // Clear failed attempts on successful login
        failedAttempts.delete(email)

        return {
          id: user.id,
          email: user.email,
          name: user.name,
          role: user.role,
          mustSetPassword: user.mustSetPassword,
        }
      },
    }),
  ],
  callbacks: {
    ...authConfig.callbacks,
    async jwt({ token, user, trigger }) {
      // Initial sign in
      if (user) {
        token.id = user.id as string
        token.role = user.role as UserRole
        token.mustSetPassword = user.mustSetPassword
      }

      // On session update, refresh from database
      if (trigger === 'update') {
        const dbUser = await prisma.user.findUnique({
          where: { id: token.id as string },
          select: { role: true, mustSetPassword: true },
        })
        if (dbUser) {
          token.role = dbUser.role
          token.mustSetPassword = dbUser.mustSetPassword
        }
      }

      return token
    },
    async session({ session, token }) {
      if (token && session.user) {
        session.user.id = token.id as string
        session.user.role = token.role as UserRole
        session.user.mustSetPassword = token.mustSetPassword as boolean | undefined
      }
      return session
    },
    async signIn({ user, account }) {
      // For email provider (magic link), check user status and get password info
      if (account?.provider === 'email') {
        const dbUser = await prisma.user.findUnique({
          where: { email: user.email! },
          select: {
            id: true,
            status: true,
            passwordHash: true,
            mustSetPassword: true,
            role: true,
          },
        })

        if (dbUser?.status === 'SUSPENDED') {
          return false // Block suspended users
        }

        // Update status from INVITED to ACTIVE on first login
        if (dbUser?.status === 'INVITED') {
          await prisma.user.update({
            where: { email: user.email! },
            data: { status: 'ACTIVE' },
          })
        }

        // Add user data for JWT callback
        if (dbUser) {
          user.id = dbUser.id
          user.role = dbUser.role
          user.mustSetPassword = dbUser.mustSetPassword || !dbUser.passwordHash
        }
      }

      // Update last login time on actual sign-in
      if (user.email) {
        await prisma.user.update({
          where: { email: user.email },
          data: { lastLoginAt: new Date() },
        }).catch(() => {
          // Ignore errors from updating last login
        })

        // Log successful login
        await prisma.auditLog.create({
          data: {
            userId: user.id as string,
            action: 'LOGIN_SUCCESS',
            entityType: 'User',
            entityId: user.id as string,
            detailsJson: { method: account?.provider || 'unknown', email: user.email },
          },
        }).catch(() => {})
      }

      return true
    },
    async redirect({ url, baseUrl }) {
      // Check if user needs to set password and redirect accordingly
      // This is called after successful authentication
      if (url.startsWith(baseUrl)) {
        return url
      }
      // Allow relative redirects
      if (url.startsWith('/')) {
        return `${baseUrl}${url}`
      }
      return baseUrl
    },
  },
})

// Helper to get session in server components
export async function getServerSession() {
  return auth()
}

// Helper to require authentication
export async function requireAuth() {
  const session = await auth()
  if (!session?.user) {
    throw new Error('Unauthorized')
  }
  return session
}

// Helper to require specific role(s)
export async function requireRole(...roles: UserRole[]) {
  const session = await requireAuth()
  if (!roles.includes(session.user.role)) {
    throw new Error('Forbidden')
  }
  return session
}
