import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  Put,
  Query,
  Req,
  Res,
  StreamableFile,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
import { ServiceKind } from "@jorgecuadros/database";
import { Request, Response } from "express";
import { downloadName, type UploadedFileLike } from "../storage/upload-file";
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 {
  PropertiesService,
  type PropertySort,
  type TrustFilter,
} from "./properties.service";
import {
  CreatePropertyDto,
  ServiceDto,
  TrustDto,
  UpdatePropertyDto,
  UpdateServiceDto,
} from "./property.dto";

const KINDS: ServiceKind[] = [
  "WATER",
  "ELECTRIC",
  "GAS",
  "CABLE",
  "PROPERTY_TAX",
  "FEDERAL_ZONE",
  "ALARM",
  "OTHER",
];

const TRUST_FILTERS: TrustFilter[] = [
  "with",
  "without",
  "active",
  "expiring",
  "expired",
  "undated",
];

const SORTS: PropertySort[] = [
  "customer",
  "address",
  "services_desc",
  "trust_due_asc",
  "trust_due_desc",
];

function parseDays(days?: string): number {
  return Math.min(365, Math.max(1, Number(days) || 30));
}

@UseGuards(AuthenticatedGuard, AbilityGuard)
@Controller("properties")
export class PropertiesController {
  constructor(
    private readonly properties: PropertiesService,
    private readonly audit: AuditService,
  ) {}

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

  @Get("stats")
  stats(@Query("days") days?: string) {
    return this.properties.stats(parseDays(days));
  }

  @Get("facets")
  facets() {
    return this.properties.facets();
  }

  @Get()
  list(
    @Query("query") query?: string,
    @Query("page") page?: string,
    @Query("pageSize") pageSize?: string,
    @Query("serviceKind") serviceKind?: string,
    @Query("municipality") municipality?: string,
    @Query("bank") bank?: string,
    @Query("trust") trust?: string,
    @Query("hasServices") hasServices?: string,
    @Query("customerId") customerId?: string,
    @Query("days") days?: string,
    @Query("includeArchived") includeArchived?: string,
    @Query("sort") sort?: string,
  ) {
    return this.properties.list({
      query,
      page: Math.max(1, Number(page) || 1),
      pageSize: Math.min(100, Math.max(1, Number(pageSize) || 25)),
      serviceKind: KINDS.includes(serviceKind as ServiceKind)
        ? (serviceKind as ServiceKind)
        : undefined,
      municipality: municipality || undefined,
      bank: bank || undefined,
      trust: TRUST_FILTERS.includes(trust as TrustFilter)
        ? (trust as TrustFilter)
        : undefined,
      hasServices:
        hasServices === "true" ? true : hasServices === "false" ? false : undefined,
      customerId: customerId || undefined,
      days: parseDays(days),
      includeArchived: includeArchived === "true",
      sort: SORTS.includes(sort as PropertySort)
        ? (sort as PropertySort)
        : "customer",
    });
  }

  @Get(":id")
  detail(@Param("id") id: string, @Query("days") days?: string) {
    return this.properties.detail(id, parseDays(days));
  }

  // --- header writes --------------------------------------------------------

  @Post()
  @RequireAbility("property:create")
  async create(@Body() dto: CreatePropertyDto, @Req() req: Request) {
    const p = await this.properties.create(dto);
    void this.audit.log(this.actingId(req), "property.create", { propertyId: p.id });
    return p;
  }

  @Patch(":id")
  @RequireAbility("property:update")
  async update(@Param("id") id: string, @Body() dto: UpdatePropertyDto, @Req() req: Request) {
    const p = await this.properties.update(id, dto);
    void this.audit.log(this.actingId(req), "property.update", { propertyId: id });
    return p;
  }

  @Delete(":id")
  @RequireAbility("property:delete")
  async archive(@Param("id") id: string, @Req() req: Request) {
    const p = await this.properties.archive(id);
    void this.audit.log(this.actingId(req), "property.archive", { propertyId: id });
    return p;
  }

  @Post(":id/restore")
  @RequireAbility("property:delete")
  async restore(@Param("id") id: string, @Req() req: Request) {
    const p = await this.properties.restore(id);
    void this.audit.log(this.actingId(req), "property.restore", { propertyId: id });
    return p;
  }

  // --- services (property:update) -------------------------------------------

  @Post(":id/services")
  @RequireAbility("property:update")
  addService(@Param("id") id: string, @Body() dto: ServiceDto) {
    return this.properties.addService(id, dto);
  }
  @Patch(":id/services/:childId")
  @RequireAbility("property:update")
  updateService(
    @Param("id") id: string,
    @Param("childId") childId: string,
    @Body() dto: UpdateServiceDto,
  ) {
    return this.properties.updateService(id, childId, dto);
  }
  @Delete(":id/services/:childId")
  @RequireAbility("property:update")
  removeService(@Param("id") id: string, @Param("childId") childId: string) {
    return this.properties.removeService(id, childId);
  }

  // --- trust account (1:1) --------------------------------------------------

  @Put(":id/trust")
  @RequireAbility("property:update")
  upsertTrust(@Param("id") id: string, @Body() dto: TrustDto) {
    return this.properties.upsertTrust(id, dto);
  }
  @Delete(":id/trust")
  @RequireAbility("property:update")
  removeTrust(@Param("id") id: string) {
    return this.properties.removeTrust(id);
  }

  // --- documents ------------------------------------------------------------

  @Post(":id/documents")
  @RequireAbility("property:update")
  @UseInterceptors(
    FileInterceptor("file", { limits: { fileSize: 50 * 1024 * 1024 } }),
  )
  addDocument(
    @Param("id") id: string,
    @UploadedFile() file: UploadedFileLike | undefined,
    @Query("type") type: string | undefined,
  ) {
    if (!file) throw new Error("No se recibió ningún archivo.");
    return this.properties.addDocument(id, file, type);
  }

  @Get(":id/documents/:childId/download")
  async downloadDocument(
    @Param("id") id: string,
    @Param("childId") childId: string,
    @Res({ passthrough: true }) res: Response,
  ): Promise<StreamableFile> {
    const { row, stream, contentType } = await this.properties.getDocument(id, childId);
    res.set({
      "Content-Type": contentType ?? "application/octet-stream",
      "Content-Disposition": `attachment; filename="${downloadName(row.storageKey, row.documentType)}"`,
    });
    return new StreamableFile(stream);
  }

  @Delete(":id/documents/:childId")
  @RequireAbility("property:update")
  removeDocument(@Param("id") id: string, @Param("childId") childId: string) {
    return this.properties.removeDocument(id, childId);
  }
}
