import {
  Body,
  Controller,
  Get,
  Param,
  Patch,
  Post,
  Query,
  Req,
  Res,
  StreamableFile,
  UploadedFiles,
  UseGuards,
  UseInterceptors,
} from "@nestjs/common";
import { FilesInterceptor } from "@nestjs/platform-express";
import type { Request, Response } 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 type { UploadedFileLike } from "../storage/upload-file";
import { PolicyOcrService } from "./policy-ocr.service";
import {
  ConfirmPolicyBatchDto,
  CreatePolicyOcrBatchDto,
  ReviewPolicyDocumentDto,
} from "./policy-ocr.dto";

/**
 * Insurance OCR intake (policy_ocr_intake).
 *
 * Mirrors StatementsController shape: one batch = one upload session of
 * policy PDFs from a provider portal (GMX today), one document per page.
 * Confirming a batch delegates nothing to a separate billing path —
 * everything goes through `Policy` (and optionally a Transaction for the
 * premium), the same tables the manual `PolicyForm` writes.
 */
@Controller("policy-ocr")
@UseGuards(AuthenticatedGuard, AbilityGuard)
export class PolicyOcrController {
  constructor(
    private readonly policyOcr: PolicyOcrService,
    private readonly audit: AuditService,
  ) {}

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

  @Get("status")
  async status() {
    return {
      ocrAvailable: await this.policyOcr.ocrAvailable(),
      storageAvailable: this.policyOcr.storageAvailable(),
    };
  }

  @Get("batches")
  listBatches(@Query("page") page?: string, @Query("pageSize") pageSize?: string) {
    return this.policyOcr.listBatches(
      Math.max(1, Number(page) || 1),
      Math.min(100, Math.max(1, Number(pageSize) || 25)),
    );
  }

  @Get("batches/:id")
  getBatch(@Param("id") id: string) {
    return this.policyOcr.getBatch(id);
  }

  @Get("batches/:id/documents")
  listDocuments(@Param("id") id: string) {
    return this.policyOcr.listDocuments(id);
  }

  /**
 * The source PDF for a parsed policy document. One PDF = one parsed policy,
 * so this returns the entire upload (typically multi-page for insurance
 * certificates). The review screen embeds it in an iframe.
 */
@Get("documents/:id/page")
async pageImage(
  @Param("id") id: string,
  @Res({ passthrough: true }) res: Response,
) {
  const { stream, contentType, contentLength } = await this.policyOcr.pageImage(id);
  res.set({
    // The doc row stores the source PDF, not a rendered page image.
    "Content-Type": contentType ?? "application/pdf",
    ...(contentLength ? { "Content-Length": String(contentLength) } : {}),
  });
  return new StreamableFile(stream);
}

  // --- writes ---------------------------------------------------------------

  @Post("batches")
  @RequireAbility("policy:ingest")
  @UseInterceptors(
    FilesInterceptor("files", 25, { limits: { fileSize: 50 * 1024 * 1024 } }),
  )
  async createBatch(
    @UploadedFiles() files: UploadedFileLike[] | undefined,
    @Body() _dto: CreatePolicyOcrBatchDto,
    @Query("label") label: string | undefined,
    @Req() req: Request,
  ) {
    const batch = await this.policyOcr.createBatch(
      files ?? [],
      this.actingId(req),
      label ?? _dto.label,
    );
    void this.audit.log(this.actingId(req), "policyOcr.batch.create", {
      batchId: batch.id,
      fileCount: batch.fileCount,
    });
    return batch;
  }

  @Patch("documents/:id")
  @RequireAbility("policy:ocr-review")
  async review(
    @Param("id") id: string,
    @Body() dto: ReviewPolicyDocumentDto,
    @Req() req: Request,
  ) {
    const doc = await this.policyOcr.review(id, dto, this.actingId(req));
    void this.audit.log(this.actingId(req), "policyOcr.document.review", {
      documentId: id,
      status: doc.status,
    });
    return doc;
  }

  @Post("documents/:id/reject")
  @RequireAbility("policy:ocr-review")
  async reject(@Param("id") id: string, @Req() req: Request) {
    const doc = await this.policyOcr.reject(id, this.actingId(req));
    void this.audit.log(this.actingId(req), "policyOcr.document.reject", {
      documentId: id,
    });
    return doc;
  }

  /** Abandon a batch pending review — rejects every unapplied page. */
  @Post("batches/:id/discard")
  @RequireAbility("policy:ocr-review")
  async discard(@Param("id") id: string, @Req() req: Request) {
    const result = await this.policyOcr.discardBatch(id, this.actingId(req));
    void this.audit.log(this.actingId(req), "policyOcr.batch.discard", {
      batchId: id,
      rejected: result.rejected,
    });
    return result;
  }

  @Post("batches/:id/confirm")
  @RequireAbility("policy:ocr-review")
  async confirm(
    @Param("id") id: string,
    @Body() dto: ConfirmPolicyBatchDto,
    @Req() req: Request,
  ) {
    const result = await this.policyOcr.confirmBatch(id, dto, this.actingId(req));
    void this.audit.log(this.actingId(req), "policyOcr.batch.confirm", {
      batchId: id,
      applied: result.applied,
      postedTransactions: result.postedTransactions,
    });
    return result;
  }
}