import {
  Body,
  Controller,
  Delete,
  ForbiddenException,
  Get,
  HttpCode,
  Param,
  Patch,
  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 { AddDomainDto } from './dto/add-domain.dto.js'
import { SetDmarcPolicyDto } from './dto/set-dmarc-policy.dto.js'
import { DomainsService, type TenantRef } from './domains.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) }
}

// Customer-admin domain management, mounted under the tenant. Same membership
// gate as the other tenant-scoped portal resources (GET :slug/users etc.): any
// member of the tenant — or a platform admin — may manage its domains.
@Controller('tenants/:slug/domains')
@UseGuards(JwtAuthGuard)
export class DomainsController {
  constructor(
    private readonly domains: DomainsService,
    private readonly tenants: TenantsService,
    private readonly actor: ActorService,
  ) {}

  // Resolve the tenant and assert the caller belongs to it (or is a platform
  // admin). Returns the lightweight ref the service works with.
  private async gate(slug: string, jwt: AuthentikJwtPayload): Promise<TenantRef> {
    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 { _id: tenant._id, slug: tenant.slug }
  }

  @Get()
  async list(@Param('slug') slug: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    const tenant = await this.gate(slug, jwt)
    return this.domains.list(tenant)
  }

  @Post()
  async add(
    @Param('slug') slug: string,
    @Body() dto: AddDomainDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const tenant = await this.gate(slug, jwt)
    const user = await this.actor.resolve(jwt)
    return this.domains.add(tenant, dto.domain, auditActor(user, req))
  }

  @Get(':domain')
  async getOne(
    @Param('slug') slug: string,
    @Param('domain') domain: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const tenant = await this.gate(slug, jwt)
    return this.domains.getOne(tenant, domain)
  }

  @Post(':domain/recheck')
  async recheck(
    @Param('slug') slug: string,
    @Param('domain') domain: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const tenant = await this.gate(slug, jwt)
    return this.domains.recheck(tenant, domain)
  }

  @Patch(':domain/dmarc')
  async setDmarc(
    @Param('slug') slug: string,
    @Param('domain') domain: string,
    @Body() dto: SetDmarcPolicyDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const tenant = await this.gate(slug, jwt)
    const user = await this.actor.resolve(jwt)
    return this.domains.setDmarcPolicy(tenant, domain, dto.dmarcPolicy, auditActor(user, req))
  }

  @Delete(':domain')
  @HttpCode(204)
  async remove(
    @Param('slug') slug: string,
    @Param('domain') domain: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const tenant = await this.gate(slug, jwt)
    const user = await this.actor.resolve(jwt)
    await this.domains.remove(tenant, domain, auditActor(user, req))
  }
}
