import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  Query,
  Req,
  Res,
  StreamableFile,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
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 {
  PoliciesService,
  type PolicySort,
  type PolicyStatus,
} from "./policies.service";
import { CreatePolicyDto, UpdatePolicyDto } from "./policy.dto";
import {
  BeneficiaryDto,
  ClaimDto,
  DriverDto,
  InstallmentDto,
  UpdateBeneficiaryDto,
  UpdateClaimDto,
  UpdateDriverDto,
  UpdateInstallmentDto,
  VehicleDto,
} from "./children.dto";

const STATUSES: PolicyStatus[] = ["active", "expiring", "expired", "undated"];
const SORTS: PolicySort[] = [
  "expiry_desc",
  "expiry_asc",
  "customer",
  "number",
  "premium_desc",
];

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

@UseGuards(AuthenticatedGuard, AbilityGuard)
@Controller("policies")
export class PoliciesController {
  constructor(
    private readonly policies: PoliciesService,
    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.policies.stats(parseDays(days));
  }

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

  @Get()
  list(
    @Query("query") query?: string,
    @Query("page") page?: string,
    @Query("pageSize") pageSize?: string,
    @Query("status") status?: string,
    @Query("days") days?: string,
    @Query("typeId") typeId?: string,
    @Query("providerId") providerId?: string,
    @Query("liquidated") liquidated?: string,
    @Query("includeArchived") includeArchived?: string,
    @Query("sort") sort?: string,
  ) {
    return this.policies.list({
      query,
      page: Math.max(1, Number(page) || 1),
      pageSize: Math.min(100, Math.max(1, Number(pageSize) || 25)),
      status: STATUSES.includes(status as PolicyStatus)
        ? (status as PolicyStatus)
        : undefined,
      days: parseDays(days),
      typeId: typeId || undefined,
      providerId: providerId || undefined,
      liquidated:
        liquidated === "true" ? true : liquidated === "false" ? false : undefined,
      includeArchived: includeArchived === "true",
      sort: SORTS.includes(sort as PolicySort)
        ? (sort as PolicySort)
        : "expiry_desc",
    });
  }

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

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

  @Post()
  @RequireAbility("policy:create")
  async create(@Body() dto: CreatePolicyDto, @Req() req: Request) {
    const p = await this.policies.create(dto);
    void this.audit.log(this.actingId(req), "policy.create", { policyId: p.id });
    return p;
  }

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

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

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

  // --- children (all editing a policy => policy:update) ---------------------

  @Post(":id/installments")
  @RequireAbility("policy:update")
  addInstallment(@Param("id") id: string, @Body() dto: InstallmentDto) {
    return this.policies.addInstallment(id, dto);
  }
  @Patch(":id/installments/:childId")
  @RequireAbility("policy:update")
  updateInstallment(
    @Param("id") id: string,
    @Param("childId") childId: string,
    @Body() dto: UpdateInstallmentDto,
  ) {
    return this.policies.updateInstallment(id, childId, dto);
  }
  @Delete(":id/installments/:childId")
  @RequireAbility("policy:update")
  removeInstallment(@Param("id") id: string, @Param("childId") childId: string) {
    return this.policies.removeInstallment(id, childId);
  }

  @Post(":id/vehicles")
  @RequireAbility("policy:update")
  addVehicle(@Param("id") id: string, @Body() dto: VehicleDto) {
    return this.policies.addVehicle(id, dto);
  }
  @Patch(":id/vehicles/:childId")
  @RequireAbility("policy:update")
  updateVehicle(
    @Param("id") id: string,
    @Param("childId") childId: string,
    @Body() dto: VehicleDto,
  ) {
    return this.policies.updateVehicle(id, childId, dto);
  }
  @Delete(":id/vehicles/:childId")
  @RequireAbility("policy:update")
  removeVehicle(@Param("id") id: string, @Param("childId") childId: string) {
    return this.policies.removeVehicle(id, childId);
  }

  @Post(":id/drivers")
  @RequireAbility("policy:update")
  addDriver(@Param("id") id: string, @Body() dto: DriverDto) {
    return this.policies.addDriver(id, dto);
  }
  @Patch(":id/drivers/:childId")
  @RequireAbility("policy:update")
  updateDriver(
    @Param("id") id: string,
    @Param("childId") childId: string,
    @Body() dto: UpdateDriverDto,
  ) {
    return this.policies.updateDriver(id, childId, dto);
  }
  @Delete(":id/drivers/:childId")
  @RequireAbility("policy:update")
  removeDriver(@Param("id") id: string, @Param("childId") childId: string) {
    return this.policies.removeDriver(id, childId);
  }

  @Post(":id/beneficiaries")
  @RequireAbility("policy:update")
  addBeneficiary(@Param("id") id: string, @Body() dto: BeneficiaryDto) {
    return this.policies.addBeneficiary(id, dto);
  }
  @Patch(":id/beneficiaries/:childId")
  @RequireAbility("policy:update")
  updateBeneficiary(
    @Param("id") id: string,
    @Param("childId") childId: string,
    @Body() dto: UpdateBeneficiaryDto,
  ) {
    return this.policies.updateBeneficiary(id, childId, dto);
  }
  @Delete(":id/beneficiaries/:childId")
  @RequireAbility("policy:update")
  removeBeneficiary(@Param("id") id: string, @Param("childId") childId: string) {
    return this.policies.removeBeneficiary(id, childId);
  }

  @Post(":id/claims")
  @RequireAbility("policy:update")
  addClaim(@Param("id") id: string, @Body() dto: ClaimDto) {
    return this.policies.addClaim(id, dto);
  }
  @Patch(":id/claims/:childId")
  @RequireAbility("policy:update")
  updateClaim(
    @Param("id") id: string,
    @Param("childId") childId: string,
    @Body() dto: UpdateClaimDto,
  ) {
    return this.policies.updateClaim(id, childId, dto);
  }
  @Delete(":id/claims/:childId")
  @RequireAbility("policy:update")
  removeClaim(@Param("id") id: string, @Param("childId") childId: string) {
    return this.policies.removeClaim(id, childId);
  }

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

  @Post(":id/documents")
  @RequireAbility("policy: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.policies.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.policies.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("policy:update")
  removeDocument(@Param("id") id: string, @Param("childId") childId: string) {
    return this.policies.removeDocument(id, childId);
  }
}
