import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  Query,
  Req,
  UseGuards,
} from "@nestjs/common";
import { Request } from "express";
import { AuthenticatedGuard } from "../auth/authenticated.guard";
import { AbilityGuard } from "../auth/ability.guard";
import { RequireAbility } from "../auth/require-ability.decorator";
import { AuditService } from "../common/audit.service";
import { CustomersService } from "./customers.service";
import { NumidService } from "./numid.service";
import { CreateCustomerDto } from "./create-customer.dto";
import { UpdateCustomerDto } from "./update-customer.dto";

@UseGuards(AuthenticatedGuard, AbilityGuard)
@Controller("customers")
export class CustomersController {
  constructor(
    private readonly customers: CustomersService,
    private readonly numids: NumidService,
    private readonly audit: AuditService,
  ) {}

  private actingId(req: Request): string {
    return (req.user as { id: string }).id;
  }

  @Get("stats")
  stats() {
    return this.customers.stats();
  }

  /** Reusable portal ids, lowest first. Declared above `:id` so the literal
   *  path is not swallowed by the wildcard route. */
  @Get("numid/candidates")
  async numidCandidates() {
    return { candidates: await this.numids.emptyCandidates() };
  }

  @Get()
  list(
    @Query("query") query?: string,
    @Query("page") page?: string,
    @Query("pageSize") pageSize?: string,
    @Query("line") line?: "utility" | "insurance" | "both",
    @Query("includeArchived") includeArchived?: string,
  ) {
    const p = Math.max(1, Number(page) || 1);
    const ps = Math.min(100, Math.max(1, Number(pageSize) || 25));
    return this.customers.list({
      query,
      page: p,
      pageSize: ps,
      line,
      includeArchived: includeArchived === "true",
    });
  }

  @Get(":id")
  detail(@Param("id") id: string) {
    return this.customers.detail(id);
  }

  @Post()
  @RequireAbility("customer:create")
  async create(@Body() dto: CreateCustomerDto, @Req() req: Request) {
    const c = await this.customers.create(dto);
    void this.audit.log(this.actingId(req), "customer.create", { customerId: c.id, name: c.name });
    return c;
  }

  @Patch(":id")
  @RequireAbility("customer:update")
  async update(
    @Param("id") id: string,
    @Body() dto: UpdateCustomerDto,
    @Req() req: Request,
  ) {
    const c = await this.customers.update(id, dto);
    void this.audit.log(this.actingId(req), "customer.update", { customerId: id });
    return c;
  }

  @Delete(":id")
  @RequireAbility("customer:delete")
  async archive(@Param("id") id: string, @Req() req: Request) {
    const c = await this.customers.archive(id);
    void this.audit.log(this.actingId(req), "customer.archive", { customerId: id });
    return c;
  }

  @Post(":id/restore")
  @RequireAbility("customer:delete")
  async restore(@Param("id") id: string, @Req() req: Request) {
    const c = await this.customers.restore(id);
    void this.audit.log(this.actingId(req), "customer.restore", { customerId: id });
    return c;
  }

  /**
   * Give this customer a portal NUMid so they can log in to
   * my.jorgecuadros.com. Idempotent — a customer who already has one gets it
   * back rather than a second identity.
   */
  @Post(":id/portal-access")
  @RequireAbility("customer:portal-access")
  async portalAccess(@Param("id") id: string, @Req() req: Request) {
    const allocation = await this.numids.allocate(id);
    if (allocation.origin !== "existing") {
      // Logged with the origin and the previous holder: a recycled id is the one
      // case where reading this record later has to answer "whose number was
      // this before, and was it taken or minted".
      void this.audit.log(this.actingId(req), "customer.portal-access", {
        customerId: id,
        numid: allocation.numid,
        origin: allocation.origin,
        previousCustomerId: allocation.previousCustomerId,
      });
    }
    return allocation;
  }
}
