import {
  Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Put, Query, Req, Res, UseGuards, UseInterceptors, UploadedFiles, ForbiddenException,
} from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import type { Response } from 'express';

import { JwtAuthGuard } from './../../auth/guards/jwt-auth.guard';
import { ReportBuilderService } from '../services/report-builder.service';
import { ReportRenderService } from '../services/report-render.service';
import { ReportWorkflowService } from '../services/report-workflow.service';
import { ClauseLibraryService } from '../services/clause-library.service';
import { ReportAiService } from '../services/report-ai.service';
import { ReportDocxService } from '../services/report-docx.service';
import { ReportImageService } from '../services/report-image.service';
import { ReportPdfService } from '../services/report-pdf.service';
import { CreateReportDto } from '../dto/create-report.dto';
import { UpdateHeaderDto } from '../dto/update-header.dto';
import { UpsertClauseDto, BulkClausesDto } from '../dto/upsert-clause.dto';
import { UpsertSectionDto, SetAttendanceDto } from '../dto/section-and-attendance.dto';
import { RaiseNcDto, ReturnReportDto, SeedLibraryDto, ImportLibraryDto, CloneLibraryDto } from '../dto/workflow.dto';

/**
 * Staff-facing API. ROUTE ORDER MATTERS: fixed paths (by-audit, clause-library) come before any ':id' route.
 */
@Controller('audit-reports')
@UseGuards(JwtAuthGuard)
export class AuditReportBuilderController {
  constructor(
    private readonly builder: ReportBuilderService,
    private readonly renderer: ReportRenderService,
    private readonly workflow: ReportWorkflowService,
    private readonly library: ClauseLibraryService,
    private readonly ai: ReportAiService,
    private readonly docx: ReportDocxService,
    private readonly imageSvc: ReportImageService,
    private readonly pdfSvc: ReportPdfService,
  ) {}

  // ───────── lists & admin (fixed paths first) ─────────
  @Get()
  list(@Query() q: any, @Req() req: any) {
    return this.builder.list(q, req.user.id);
  }

  @Get('by-audit/:rowId')
  byAudit(@Param('rowId', ParseIntPipe) rowId: number, @Req() req: any) {
    return this.builder.byAudit(rowId, req.user.id);
  }

  /** Stage guess, booked standards and existing reports for one audit row (use before POST /audit-reports). */
  @Get('context/:rowId')
  context(@Param('rowId', ParseIntPipe) rowId: number, @Req() req: any) {
    return this.builder.context(rowId, req.user.id);
  }

  @Get('clause-library/:standardId')
  clauseLibrary(@Param('standardId', ParseIntPipe) standardId: number, @Query('scope') scope?: 'readiness' | 'full') {
    return this.library.list(standardId, scope);
  }

  /** Seed a built-in clause set: key = iso9001 | iso14001 (see SEED_SETS). Body { standard_id, overwrite_evidence? }. Admin / scheme only. */
  @Post('clause-library/seed/:key')
  async seedLibrary(@Param('key') key: string, @Body() dto: SeedLibraryDto, @Req() req: any) {
    if (!(await this.builder.canApprove(req.user.id))) throw new ForbiddenException('Admin / scheme department only.');
    return this.library.seedStandard(key, dto.standard_id, !!dto.overwrite_evidence);
  }

  /** Add / update clause rows for any standard (ISO 27001, 14001 ...). Admin / scheme only. */
  @Post('clause-library/import')
  async importLibrary(@Body() dto: ImportLibraryDto, @Req() req: any) {
    if (!(await this.builder.canApprove(req.user.id))) throw new ForbiddenException('Admin / scheme department only.');
    return this.library.importRows(dto.standard_id, dto.scope ?? 'full', dto.rows);
  }

  /** Copy a standard's clause rows to another standard as a starting point. Admin / scheme only. */
  @Post('clause-library/clone')
  async cloneLibrary(@Body() dto: CloneLibraryDto, @Req() req: any) {
    if (!(await this.builder.canApprove(req.user.id))) throw new ForbiddenException('Admin / scheme department only.');
    return this.library.cloneStandard(dto.from_standard_id, dto.to_standard_id);
  }

  // ───────── create ─────────
  @Post()
  create(@Body() dto: CreateReportDto, @Req() req: any) {
    return this.builder.create(dto, req.user.id);
  }

  // ───────── read ─────────
  @Get(':id')
  get(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
    return this.builder.getEditorModel(id, req.user.id);
  }

  /** HTML preview, same layout as the PDF. Draft reports show a DRAFT watermark. */
  @Get(':id/preview')
  async preview(@Param('id', ParseIntPipe) id: number, @Req() req: any, @Res() res: Response) {
    await this.builder.assertAccess(await this.builder.getReportOrFail(id), req.user.id);
    res.type('html').send(await this.renderer.renderHtml(id, 'all'));
  }

  /** Approved reports stream the stored final PDF; everything else is rendered live as a DRAFT. */
  @Get(':id/pdf')
  async pdf(@Param('id', ParseIntPipe) id: number, @Req() req: any, @Res() res: Response) {
    const r = await this.builder.getReportOrFail(id);
    await this.builder.assertAccess(r, req.user.id);
    res.setHeader('Content-Type', 'application/pdf');
    if (['approved', 'locked'].includes(r.status) && r.pdf_path) {
      const f = await this.workflow.openFinalPdf(id);
      res.setHeader('Content-Disposition', `inline; filename="${f.filename}"`);
      res.setHeader('Content-Length', String(f.size));
      return f.stream.pipe(res);
    }
    const { vm } = await this.renderer.getViewModel(id);
    vm.meta = { ...(vm.meta ?? {}), draft: true };
    const buf = await this.pdfSvc.buildFromVm(vm, r);
    res.setHeader('Content-Disposition', `inline; filename="${r.report_no ?? `report-${id}`}-DRAFT.pdf"`);
    res.end(buf);
  }

  /** Word (.docx) version of the report: same layout as the PDF. Draft reports are marked DRAFT. */
  @Get(':id/docx')
  async wordFile(@Param('id', ParseIntPipe) id: number, @Req() req: any, @Res() res: Response) {
    await this.builder.assertAccess(await this.builder.getReportOrFail(id), req.user.id);
    const { buffer, filename } = await this.docx.build(id);
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
    res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
    res.setHeader('Content-Length', String(buffer.length));
    res.end(buffer);
  }

  // ───────── AI drafting (auditor reviews and edits afterwards) ─────────
  @Post(':id/ai/evidence')
  aiEvidence(
    @Param('id', ParseIntPipe) id: number,
    @Body() body: { clause_ids?: number[]; overwrite?: boolean; mark_conforming?: boolean },
    @Req() req: any,
  ) {
    return this.ai.generateEvidence(id, req.user.id, body ?? {});
  }

  /** Helps the auditor word a finding: short notes in, formal NC / observation statement out. */
  @Post(':id/ai/finding')
  aiFinding(
    @Param('id', ParseIntPipe) id: number,
    @Body() body: { clause_id: number; note?: string; nc_type?: 'Major' | 'Minor' | 'Observation' },
    @Req() req: any,
  ) {
    return this.ai.generateFinding(id, req.user.id, body);
  }

  @Post(':id/ai/sections')
  aiSections(
    @Param('id', ParseIntPipe) id: number,
    @Body() body: { keys?: string[]; overwrite?: boolean },
    @Req() req: any,
  ) {
    return this.ai.generateSections(id, req.user.id, body ?? {});
  }

  // ───────── photos (products / premises / evidence) ─────────
  @Get(':id/images')
  listImages(@Param('id', ParseIntPipe) id: number, @Query('section_key') section_key: string, @Query('clause_id') clause_id: string, @Req() req: any) {
    return this.imageSvc.list(id, req.user.id, { section_key: section_key || undefined, clause_id: clause_id ? Number(clause_id) : undefined });
  }

  @Post(':id/images')
  @UseInterceptors(FilesInterceptor('files', 20, { storage: memoryStorage(), limits: { fileSize: 12 * 1024 * 1024 } }))
  uploadImages(
    @Param('id', ParseIntPipe) id: number,
    @UploadedFiles() files: any[],
    @Body() body: { section_key?: string; clause_id?: string; caption?: string },
    @Req() req: any,
  ) {
    return this.imageSvc.upload(id, req.user.id, files ?? [], body ?? {});
  }

  @Put(':id/images-order')
  reorderImages(@Param('id', ParseIntPipe) id: number, @Body() body: { ids: number[] }, @Req() req: any) {
    return this.imageSvc.reorder(id, req.user.id, body?.ids ?? []);
  }

  @Get(':id/images/:imageId/file')
  async imageFile(@Param('id', ParseIntPipe) id: number, @Param('imageId', ParseIntPipe) imageId: number, @Req() req: any, @Res() res: Response) {
    const f = await this.imageSvc.fileFor(id, imageId, req.user.id);
    res.setHeader('Content-Type', f.mime);
    res.setHeader('Cache-Control', 'private, max-age=300');
    res.sendFile(f.abs);
  }

  @Patch(':id/images/:imageId')
  updateImage(@Param('id', ParseIntPipe) id: number, @Param('imageId', ParseIntPipe) imageId: number, @Body() body: { caption?: string; section_key?: string }, @Req() req: any) {
    return this.imageSvc.update(id, imageId, req.user.id, body ?? {});
  }

  @Delete(':id/images/:imageId')
  deleteImage(@Param('id', ParseIntPipe) id: number, @Param('imageId', ParseIntPipe) imageId: number, @Req() req: any) {
    return this.imageSvc.remove(id, imageId, req.user.id);
  }

  // ───────── edit ─────────
  @Patch(':id/header')
  header(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateHeaderDto, @Req() req: any) {
    return this.builder.updateHeader(id, dto, req.user.id);
  }

  @Put(':id/clauses')
  bulk(@Param('id', ParseIntPipe) id: number, @Body() dto: BulkClausesDto, @Req() req: any) {
    return this.builder.bulkClauses(id, dto, req.user.id);
  }

  @Put(':id/clauses/:clauseId')
  clause(
    @Param('id', ParseIntPipe) id: number,
    @Param('clauseId', ParseIntPipe) clauseId: number,
    @Body() dto: UpsertClauseDto,
    @Req() req: any,
  ) {
    return this.builder.upsertClause(id, clauseId, dto, req.user.id);
  }

  @Post(':id/clauses/:clauseId/raise-nc')
  raiseNc(
    @Param('id', ParseIntPipe) id: number,
    @Param('clauseId', ParseIntPipe) clauseId: number,
    @Body() dto: RaiseNcDto,
    @Req() req: any,
  ) {
    return this.builder.raiseNc(id, clauseId, dto, req.user.id);
  }

  @Put(':id/sections/:key')
  section(@Param('id', ParseIntPipe) id: number, @Param('key') key: string, @Body() dto: UpsertSectionDto, @Req() req: any) {
    return this.builder.upsertSection(id, key, dto.body, req.user.id);
  }

  @Put(':id/attendance')
  attendance(@Param('id', ParseIntPipe) id: number, @Body() dto: SetAttendanceDto, @Req() req: any) {
    return this.builder.setAttendance(id, dto.attendees, req.user.id);
  }

  @Post(':id/refresh')
  refresh(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
    return this.builder.refreshFromCrm(id, req.user.id);
  }

  // ───────── workflow ─────────
  @Post(':id/submit')
  submit(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
    return this.workflow.submit(id, req.user.id);
  }

  @Post(':id/approve')
  approve(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
    return this.workflow.approve(id, req.user.id);
  }

  @Post(':id/return')
  returnReport(@Param('id', ParseIntPipe) id: number, @Body() dto: ReturnReportDto, @Req() req: any) {
    return this.workflow.returnToAuditor(id, dto.reason, req.user.id);
  }

  @Post(':id/reopen')
  reopen(@Param('id', ParseIntPipe) id: number, @Body() dto: ReturnReportDto, @Req() req: any) {
    return this.workflow.reopen(id, dto.reason, req.user.id);
  }

  @Post(':id/lock')
  lock(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
    return this.workflow.lock(id, req.user.id);
  }
}
