import {
  Body,
  Controller,
  Delete,
  ForbiddenException,
  Get,
  HttpCode,
  Param,
  Patch,
  Post,
  Req,
  UseGuards,
} from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { ActorService } from '../auth/actor.service.js'
import { clientIp } from '../auth/client-ip.js'
import { CurrentUser } from '../auth/current-user.decorator.js'
import { JwtAuthGuard } from '../auth/jwt-auth.guard.js'
import type { AuthentikJwtPayload } from '../auth/jwt-payload.interface.js'
import { OperatorGuard } from '../auth/operator.guard.js'
import { CreateUserDto } from './dto/create-user.dto.js'
import { InviteOperatorDto } from './dto/invite-operator.dto.js'
import { UpdateUserDto } from './dto/update-user.dto.js'
import { UsersService } from './users.service.js'

// Authentik group name that grants platform-wide admin in Dezky. This is the ONLY
// place we look at the JWT's groups claim outside of /users/me — and even here it's
// just for the bootstrap: the resulting platformAdmin boolean on the User doc is
// what every other endpoint reads.
const ADMIN_BOOTSTRAP_GROUP_DEFAULT = 'dezky-platform-admins'

@Controller('users')
@UseGuards(JwtAuthGuard)
export class UsersController {
  private readonly adminBootstrapGroup: string

  constructor(
    private readonly users: UsersService,
    private readonly actor: ActorService,
    config: ConfigService,
  ) {
    this.adminBootstrapGroup =
      config.get<string>('PLATFORM_ADMIN_BOOTSTRAP_GROUP') ?? ADMIN_BOOTSTRAP_GROUP_DEFAULT
  }

  // The signed-in user's own profile — bootstraps the user record on first call,
  // and syncs name/email/tenants/platformAdmin from the JWT on every subsequent call.
  // Adds a `partner` field when User.partnerId is set so the portal can decide
  // whether to render the partner-admin surface or the end-user surface.
  @Get('me')
  async me(@CurrentUser() jwt: AuthentikJwtPayload) {
    return this.users.meWithPartner({
      subject: jwt.sub,
      email: jwt.email ?? jwt.preferred_username ?? jwt.sub,
      name: jwt.name ?? jwt.preferred_username ?? jwt.email ?? jwt.sub,
      tenantSlugs: jwt.groups ?? [],
      platformAdmin: jwt.groups?.includes(this.adminBootstrapGroup) ?? false,
    })
  }

  // Partner-scoped endpoints live in PartnerMeController under /me/partner.
  // Identity endpoints (above) stay here.

  @Post()
  async create(@Body() dto: CreateUserDto, @CurrentUser() jwt: AuthentikJwtPayload) {
    const actor = await this.actor.resolve(jwt)
    if (!actor.platformAdmin) {
      throw new ForbiddenException('Only platform admins can create users directly')
    }
    return this.users.create(dto)
  }

  // Operator-only: invite a new platform admin. Creates the user in Authentik,
  // adds them to the dezky-platform-admins group, returns a recovery link the
  // operator shares manually. Once outbound SMTP is wired, Authentik can
  // email the link directly and the response link is mostly informational.
  @Post('invite')
  @UseGuards(OperatorGuard)
  async invite(
    @Body() dto: InviteOperatorDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const actor = await this.actor.resolve(jwt)
    return this.users.inviteOperator(dto, {
      userId: String(actor._id),
      email: actor.email,
      ip: clientIp(req),
    })
  }

  @Get()
  async findAll(@CurrentUser() jwt: AuthentikJwtPayload) {
    const actor = await this.actor.resolve(jwt)
    if (actor.platformAdmin) return this.users.findAll()
    return this.users.findAllForTenants(actor.tenantIds)
  }

  @Get(':subject')
  async findOne(@Param('subject') subject: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    const actor = await this.actor.resolve(jwt)
    if (subject !== jwt.sub && !actor.platformAdmin) {
      throw new ForbiddenException('Cannot read other users')
    }
    return this.users.findOneBySubject(subject)
  }

  @Patch(':subject')
  async update(
    @Param('subject') subject: string,
    @Body() dto: UpdateUserDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const actor = await this.actor.resolve(jwt)
    if (!actor.platformAdmin) {
      throw new ForbiddenException('Only platform admins can update users')
    }
    return this.users.update(subject, dto)
  }

  @Delete(':subject')
  @HttpCode(204)
  async deactivate(
    @Param('subject') subject: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const actor = await this.actor.resolve(jwt)
    if (!actor.platformAdmin) {
      throw new ForbiddenException('Only platform admins can deactivate users')
    }
    await this.users.deactivate(subject, {
      userId: String(actor._id),
      email: actor.email,
      ip: clientIp(req),
    })
  }
}
