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 { CreateAliasDto } from './dto/create-alias.dto.js'
import { CreateListDto } from './dto/create-list.dto.js'
import { UpdateListDto } from './dto/update-list.dto.js'
import { MailService, type TenantRef } from './mail.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 Mail settings: aliases + distribution lists (the per-tenant
// parts of Stalwart mail config). Forwarding / spam filters / retention are
// server-global and stay operator-managed, so they aren't exposed here.
@Controller('tenants/:slug/mail')
@UseGuards(JwtAuthGuard)
export class MailController {
  constructor(
    private readonly mail: MailService,
    private readonly tenants: TenantsService,
    private readonly actor: ActorService,
  ) {}

  private async gate(slug: string, jwt: AuthentikJwtPayload): Promise<{ actor: any; tenant: 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 { actor, tenant: { _id: tenant._id, slug: tenant.slug } }
  }

  // ── Aliases ──
  @Get('aliases')
  async listAliases(@Param('slug') slug: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    const { tenant } = await this.gate(slug, jwt)
    return this.mail.listAliases(tenant)
  }

  @Post('aliases')
  async createAlias(
    @Param('slug') slug: string,
    @Body() dto: CreateAliasDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    return this.mail.createAlias(tenant, dto, auditActor(actor, req))
  }

  @Delete('aliases/:address')
  @HttpCode(204)
  async deleteAlias(
    @Param('slug') slug: string,
    @Param('address') address: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    await this.mail.deleteAlias(tenant, address, auditActor(actor, req))
  }

  // ── Distribution lists ──
  @Get('lists')
  async listLists(@Param('slug') slug: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    const { tenant } = await this.gate(slug, jwt)
    return this.mail.listLists(tenant)
  }

  @Post('lists')
  async createList(
    @Param('slug') slug: string,
    @Body() dto: CreateListDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    return this.mail.createList(tenant, dto, auditActor(actor, req))
  }

  @Patch('lists/:id')
  async updateList(
    @Param('slug') slug: string,
    @Param('id') id: string,
    @Body() dto: UpdateListDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    return this.mail.updateListRecipients(tenant, id, dto.recipients, auditActor(actor, req))
  }

  @Delete('lists/:id')
  @HttpCode(204)
  async deleteList(
    @Param('slug') slug: string,
    @Param('id') id: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
    @Req() req: Parameters<typeof clientIp>[0],
  ) {
    const { actor, tenant } = await this.gate(slug, jwt)
    await this.mail.deleteList(tenant, id, auditActor(actor, req))
  }
}
