import type { FastifyPluginAsync } from 'fastify'
import { eq } from 'drizzle-orm'
import bcrypt from 'bcryptjs'
import { RegisterSchema, LoginSchema, PinLoginSchema, SetPinSchema } from '@lunarfront/shared/schemas'
import { users } from '../../db/schema/users.js'

const SALT_ROUNDS = 10

export const authRoutes: FastifyPluginAsync = async (app) => {
  // Rate limit auth routes — 10 attempts per 15 minutes per IP
  const rateLimitConfig = {
    config: {
      rateLimit: {
        max: 10,
        timeWindow: '15 minutes',
      },
    },
  }

  app.post('/auth/register', rateLimitConfig, async (request, reply) => {
    const parsed = RegisterSchema.safeParse(request.body)
    if (!parsed.success) {
      return reply.status(400).send({
        error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 },
      })
    }

    const { email, password, firstName, lastName, role } = parsed.data

    // Email is globally unique
    const existing = await app.db
      .select({ id: users.id })
      .from(users)
      .where(eq(users.email, email))
      .limit(1)

    if (existing.length > 0) {
      return reply.status(409).send({
        error: { message: 'User with this email already exists', statusCode: 409 },
      })
    }

    const passwordHash = await bcrypt.hash(password, SALT_ROUNDS)

    const [user] = await app.db
      .insert(users)
      .values({
        email,
        passwordHash,
        firstName,
        lastName,
        role,
      })
      .returning({
        id: users.id,
        email: users.email,
        firstName: users.firstName,
        lastName: users.lastName,
        role: users.role,
        createdAt: users.createdAt,
      })

    const token = app.jwt.sign({
      id: user.id,
      role: user.role,
    })

    request.log.info({ userId: user.id, email: user.email }, 'User registered')
    return reply.status(201).send({ user, token })
  })

  app.post('/auth/login', rateLimitConfig, async (request, reply) => {
    const parsed = LoginSchema.safeParse(request.body)
    if (!parsed.success) {
      return reply.status(400).send({
        error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 },
      })
    }

    const { email, password } = parsed.data

    // Email is globally unique — company is derived from the user record
    const [user] = await app.db
      .select()
      .from(users)
      .where(eq(users.email, email))
      .limit(1)

    if (!user) {
      request.log.warn({ email }, 'Login failed — unknown email')
      return reply.status(401).send({
        error: { message: 'Invalid email or password', statusCode: 401 },
      })
    }

    const valid = await bcrypt.compare(password, user.passwordHash)
    if (!valid) {
      request.log.warn({ email, userId: user.id }, 'Login failed — wrong password')
      return reply.status(401).send({
        error: { message: 'Invalid email or password', statusCode: 401 },
      })
    }

    const token = app.jwt.sign({
      id: user.id,
      role: user.role,
    })

    request.log.info({ userId: user.id, email }, 'User logged in')
    return reply.send({
      user: {
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        role: user.role,
      },
      token,
    })
  })

  // Change own password
  app.post('/auth/change-password', { preHandler: [app.authenticate] }, async (request, reply) => {
    const { currentPassword, newPassword } = request.body as { currentPassword?: string; newPassword?: string }
    if (!currentPassword || !newPassword) {
      return reply.status(400).send({ error: { message: 'currentPassword and newPassword are required', statusCode: 400 } })
    }
    if (newPassword.length < 12) {
      return reply.status(400).send({ error: { message: 'Password must be at least 12 characters', statusCode: 400 } })
    }

    const [user] = await app.db.select().from(users).where(eq(users.id, request.user.id)).limit(1)
    if (!user) return reply.status(404).send({ error: { message: 'User not found', statusCode: 404 } })

    const valid = await bcrypt.compare(currentPassword, user.passwordHash)
    if (!valid) {
      return reply.status(401).send({ error: { message: 'Current password is incorrect', statusCode: 401 } })
    }

    const newHash = await bcrypt.hash(newPassword, SALT_ROUNDS)
    await app.db.update(users).set({ passwordHash: newHash, updatedAt: new Date() }).where(eq(users.id, request.user.id))

    request.log.info({ userId: request.user.id }, 'Password changed')
    return reply.send({ message: 'Password changed' })
  })

  // Admin: generate password reset token for a user
  app.post('/auth/reset-password/:userId', { preHandler: [app.authenticate, app.requirePermission('users.admin')] }, async (request, reply) => {
    const { userId } = request.params as { userId: string }

    const [user] = await app.db.select({ id: users.id, email: users.email }).from(users).where(eq(users.id, userId)).limit(1)
    if (!user) return reply.status(404).send({ error: { message: 'User not found', statusCode: 404 } })

    // Generate a signed reset token that expires in 1 hour
    const resetToken = app.jwt.sign({ userId: user.id, purpose: 'password-reset' } as any, { expiresIn: '1h' })
    const resetLink = `${process.env.APP_URL ?? 'http://localhost:5173'}/reset-password?token=${resetToken}`

    request.log.info({ userId, generatedBy: request.user.id }, 'Password reset link generated')
    return reply.send({ resetLink, expiresIn: '1 hour' })
  })

  // Reset password with token
  app.post('/auth/reset-password', async (request, reply) => {
    const { token, newPassword } = request.body as { token?: string; newPassword?: string }
    if (!token || !newPassword) {
      return reply.status(400).send({ error: { message: 'token and newPassword are required', statusCode: 400 } })
    }
    if (newPassword.length < 12) {
      return reply.status(400).send({ error: { message: 'Password must be at least 12 characters', statusCode: 400 } })
    }

    try {
      const payload = app.jwt.verify<{ userId: string; purpose: string }>(token)
      if (payload.purpose !== 'password-reset') {
        return reply.status(400).send({ error: { message: 'Invalid reset token', statusCode: 400 } })
      }

      const newHash = await bcrypt.hash(newPassword, SALT_ROUNDS)
      await app.db.update(users).set({ passwordHash: newHash, updatedAt: new Date() }).where(eq(users.id, payload.userId))

      request.log.info({ userId: payload.userId }, 'Password reset via token')
      return reply.send({ message: 'Password reset successfully' })
    } catch {
      return reply.status(400).send({ error: { message: 'Invalid or expired reset token', statusCode: 400 } })
    }
  })

  // Get current user profile
  app.get('/auth/me', { preHandler: [app.authenticate] }, async (request, reply) => {
    const [user] = await app.db
      .select({
        id: users.id,
        email: users.email,
        firstName: users.firstName,
        lastName: users.lastName,
        role: users.role,
        createdAt: users.createdAt,
      })
      .from(users)
      .where(eq(users.id, request.user.id))
      .limit(1)

    if (!user) return reply.status(404).send({ error: { message: 'User not found', statusCode: 404 } })
    return reply.send(user)
  })

  // Update current user profile
  app.patch('/auth/me', { preHandler: [app.authenticate] }, async (request, reply) => {
    const { firstName, lastName } = request.body as { firstName?: string; lastName?: string }

    const updates: Record<string, unknown> = { updatedAt: new Date() }
    if (firstName) updates.firstName = firstName
    if (lastName) updates.lastName = lastName

    const [user] = await app.db
      .update(users)
      .set(updates)
      .where(eq(users.id, request.user.id))
      .returning({
        id: users.id,
        email: users.email,
        firstName: users.firstName,
        lastName: users.lastName,
        role: users.role,
      })

    return reply.send(user)
  })

  // PIN login — for POS unlock, no JWT required to call
  app.post('/auth/pin-login', rateLimitConfig, async (request, reply) => {
    const parsed = PinLoginSchema.safeParse(request.body)
    if (!parsed.success) {
      return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
    }

    const { code } = parsed.data
    // First 4 digits = employee number, rest = PIN
    const employeeNumber = code.slice(0, 4)
    const pin = code.slice(4)

    if (!pin) {
      return reply.status(401).send({ error: { message: 'Invalid code', statusCode: 401 } })
    }

    const [user] = await app.db
      .select()
      .from(users)
      .where(eq(users.employeeNumber, employeeNumber))
      .limit(1)

    if (!user || !user.isActive || !user.pinHash) {
      return reply.status(401).send({ error: { message: 'Invalid code', statusCode: 401 } })
    }

    const match = await bcrypt.compare(pin, user.pinHash)
    if (!match) {
      return reply.status(401).send({ error: { message: 'Invalid code', statusCode: 401 } })
    }

    const token = app.jwt.sign({ id: user.id, role: user.role }, { expiresIn: '8h' })
    request.log.info({ userId: user.id }, 'PIN login')
    return reply.send({
      user: {
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        role: user.role,
      },
      token,
    })
  })

  // Set PIN — requires full auth
  app.post('/auth/set-pin', { preHandler: [app.authenticate] }, async (request, reply) => {
    const parsed = SetPinSchema.safeParse(request.body)
    if (!parsed.success) {
      return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
    }

    const pinHash = await bcrypt.hash(parsed.data.pin, SALT_ROUNDS)
    await app.db.update(users).set({ pinHash, updatedAt: new Date() }).where(eq(users.id, request.user.id))

    request.log.info({ userId: request.user.id }, 'POS PIN set')
    return reply.send({ message: 'PIN set' })
  })

  // Remove PIN — requires full auth
  app.delete('/auth/pin', { preHandler: [app.authenticate] }, async (request, reply) => {
    await app.db.update(users).set({ pinHash: null, updatedAt: new Date() }).where(eq(users.id, request.user.id))

    request.log.info({ userId: request.user.id }, 'POS PIN removed')
    return reply.send({ message: 'PIN removed' })
  })
}
