import {
  Body,
  Controller,
  Delete,
  ForbiddenException,
  HttpCode,
  Param,
  Post,
  Req,
  UseGuards,
} from '@nestjs/common'
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 type { AuditActor } from '../audit/audit.service.js'
import { TenantsService } from '../tenants/tenants.service.js'
import { CreateTenantMemberDto } from './dto/create-tenant-member.dto.js'
import { UsersService } from './users.service.js'

function auditActor(
  user: { _id: unknown; email: string },
  req: Parameters<typeof clientIp>[0],
): AuditActor {
  return { userId: String(user._id), email: user.email, ip: clientIp(req) }
}

// Create a workspace member (the Users & groups "Invite user" flow). Mounted
// under the tenant alongside GET /tenants/:slug/users (which lives in
// TenantsController); same membership gate as the other tenant-scoped resources.
// Lives in UsersModule because the cross-system provisioning is in UsersService,
// and TenantsModule can't import UsersModule (UsersModule already imports it).
@Controller('tenants/:slug/users')
@UseGuards(JwtAuthGuard)
export class TenantMembersController {
  constructor(
    private readonly users: UsersService,
    private readonly tenants: TenantsService,
    private readonly actor: ActorService,
  ) {}

  @Post()
  async create(
    @Param('slug') slug: string,
    @Body() dto: CreateTenantMemberDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const actor = await this.actor.resolve(jwt)
    const tenant = await this.tenants.findOneBySlug(slug)
    if (!actor.platformAdmin && !actor.tenantIds.some((id) => id.equals(tenant._id))) {
      throw new ForbiddenException(`No access to tenant "${slug}"`)
    }
    return this.users.createTenantMember(
      { _id: tenant._id, slug: tenant.slug, authentikGroupId: tenant.authentikGroupId },
      dto,
      auditActor(actor, req),
    )
  }

  // Remove a member and tear down their provisioned accounts. Self-removal is
  // blocked so an admin can't lock themselves out of their own workspace.
  @Delete(':userId')
  @HttpCode(204)
  async remove(
    @Param('slug') slug: string,
    @Param('userId') userId: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const actor = await this.actor.resolve(jwt)
    const tenant = await this.tenants.findOneBySlug(slug)
    if (!actor.platformAdmin && !actor.tenantIds.some((id) => id.equals(tenant._id))) {
      throw new ForbiddenException(`No access to tenant "${slug}"`)
    }
    if (String(actor._id) === userId) {
      throw new ForbiddenException('You can’t remove your own account.')
    }
    await this.users.removeTenantMember(
      { _id: tenant._id, slug: tenant.slug, authentikGroupId: tenant.authentikGroupId },
      userId,
      auditActor(actor, req),
    )
  }

  // Resolve the tenant + assert the caller belongs to it (or is a platform admin).
  private async gate(slug: string, jwt: AuthentikJwtPayload) {
    const actor = await this.actor.resolve(jwt)
    const tenant = await this.tenants.findOneBySlug(slug)
    if (!actor.platformAdmin && !actor.tenantIds.some((id) => id.equals(tenant._id))) {
      throw new ForbiddenException(`No access to tenant "${slug}"`)
    }
    return { actor, tenant }
  }

  @Post(':userId/suspend')
  @HttpCode(204)
  async suspend(
    @Param('slug') slug: string,
    @Param('userId') userId: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    if (String(actor._id) === userId) {
      throw new ForbiddenException('You can’t suspend your own account.')
    }
    await this.users.setMemberSuspended(
      { _id: tenant._id, slug: tenant.slug },
      userId,
      true,
      auditActor(actor, req),
    )
  }

  @Post(':userId/resume')
  @HttpCode(204)
  async resume(
    @Param('slug') slug: string,
    @Param('userId') userId: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    await this.users.setMemberSuspended(
      { _id: tenant._id, slug: tenant.slug },
      userId,
      false,
      auditActor(actor, req),
    )
  }

  @Post(':userId/force-logout')
  async forceLogout(
    @Param('slug') slug: string,
    @Param('userId') userId: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    if (String(actor._id) === userId) {
      throw new ForbiddenException('You can’t force-logout your own session here.')
    }
    return this.users.forceLogoutMember(
      { _id: tenant._id, slug: tenant.slug },
      userId,
      auditActor(actor, req),
    )
  }

  @Post(':userId/reset-password')
  async resetPassword(
    @Param('slug') slug: string,
    @Param('userId') userId: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    return this.users.resetMemberPassword(
      { _id: tenant._id, slug: tenant.slug },
      userId,
      auditActor(actor, req),
    )
  }
}
