import fp from 'fastify-plugin'
import fjwt from '@fastify/jwt'
import { eq } from 'drizzle-orm'
import { users } from '../db/schema/users.js'
import { RbacService } from '../services/rbac.service.js'

declare module 'fastify' {
  interface FastifyRequest {
    locationId: string
    user: { id: string; role: string }
    permissions: Set<string>
  }
}

declare module '@fastify/jwt' {
  interface FastifyJWT {
    payload: { id: string; role: string }
    user: { id: string; role: string }
  }
}

/**
 * Permission inheritance: admin implies edit implies view for the same domain.
 * e.g. having "accounts.admin" means you also have "accounts.edit" and "accounts.view"
 */
const ACTION_HIERARCHY: Record<string, string[]> = {
  admin: ['admin', 'edit', 'view'],
  edit: ['edit', 'view'],
  view: ['view'],
  // Non-hierarchical actions (files, reports) don't cascade
  upload: ['upload'],
  delete: ['delete'],
  send: ['send'],
  export: ['export'],
}

function expandPermissions(slugs: string[]): Set<string> {
  const expanded = new Set<string>()
  for (const slug of slugs) {
    expanded.add(slug)
    const [domain, action] = slug.split('.')
    const implied = ACTION_HIERARCHY[action]
    if (implied && domain) {
      for (const a of implied) {
        expanded.add(`${domain}.${a}`)
      }
    }
  }
  return expanded
}

export const authPlugin = fp(async (app) => {
  const secret = process.env.JWT_SECRET
  if (!secret) {
    throw new Error('JWT_SECRET environment variable is required')
  }

  await app.register(fjwt, {
    secret,
    sign: { expiresIn: '24h' },
  })

  app.addHook('onRequest', async (request) => {
    request.locationId = (request.headers['x-location-id'] as string) ?? ''
    request.permissions = new Set()
  })

  app.decorate('authenticate', async function (request: any, reply: any) {
    try {
      await request.jwtVerify()

      // Check if user account is active
      const [dbUser] = await app.db
        .select({ isActive: users.isActive })
        .from(users)
        .where(eq(users.id, request.user.id))
        .limit(1)

      if (!dbUser || !dbUser.isActive) {
        reply.status(401).send({ error: { message: 'Account disabled', statusCode: 401 } })
        return
      }

      // Load permissions from DB and expand with inheritance
      const permSlugs = await RbacService.getUserPermissions(app.db, request.user.id)
      request.permissions = expandPermissions(permSlugs)
    } catch {
      reply.status(401).send({ error: { message: 'Unauthorized', statusCode: 401 } })
    }
  })

  app.decorate('requirePermission', function (...requiredPermissions: string[]) {
    return async function (request: any, reply: any) {
      // If user has no permissions loaded (shouldn't happen after authenticate), deny
      if (!request.permissions || request.permissions.size === 0) {
        reply.status(403).send({ error: { message: 'No permissions assigned', statusCode: 403 } })
        return
      }

      // Check if user has ANY of the required permissions
      const hasPermission = requiredPermissions.some((p) => request.permissions.has(p))
      if (!hasPermission) {
        reply.status(403).send({
          error: {
            message: 'Insufficient permissions',
            statusCode: 403,
            required: requiredPermissions,
          },
        })
      }
    }
  })

  // Keep legacy requireRole for backward compatibility during migration
  app.decorate('requireRole', function (...roles: string[]) {
    return async function (request: any, reply: any) {
      if (!roles.includes(request.user.role)) {
        reply
          .status(403)
          .send({ error: { message: 'Insufficient permissions', statusCode: 403 } })
      }
    }
  })
})

declare module 'fastify' {
  interface FastifyInstance {
    authenticate: (request: any, reply: any) => Promise<void>
    requirePermission: (...permissions: string[]) => (request: any, reply: any) => Promise<void>
    requireRole: (...roles: string[]) => (request: any, reply: any) => Promise<void>
  }
}
