import {
  Controller, Get, Post, Patch, Delete,
  Param, Body, Query, Res, HttpCode, HttpStatus,
  UseInterceptors, UploadedFile, BadRequestException,
} from '@nestjs/common'
import { FileInterceptor } from '@nestjs/platform-express'
import { diskStorage } from 'multer'
import * as path from 'path'
import * as fs from 'fs'
import type { Response } from 'express'
import { FormTemplatesService } from './form-templates.service'
import { FormCalibrationAgent } from './form-calibration.agent'
import { CreateFormTemplateDto } from './dto/create-form-template.dto'
import { UpdateFormTemplateDto } from './dto/update-form-template.dto'
import { STOCK_FORMS } from './stock-library'

// IMPORTANT: In NestJS, within a single controller all static paths MUST be declared
// before parameterised paths (e.g. ':id'). NestJS matches routes in declaration order
// and ':id' will greedily catch any literal path segment that comes after it.

@Controller('form-templates')
export class FormTemplatesController {
  constructor(
    private readonly svc: FormTemplatesService,
    private readonly agent: FormCalibrationAgent,
  ) {}

  // ── Static collection routes ───────────────────────────────────────────────

  @Get()
  findAll(@Query('practiceArea') practiceArea?: string) {
    return this.svc.findAll(practiceArea)
  }

  @Post()
  create(@Body() dto: CreateFormTemplateDto) {
    return this.svc.create(dto)
  }

  // ── Static sub-collection routes (must come before :id) ───────────────────

  @Get('logs/matter/:matterId')
  listLogs(@Param('matterId') matterId: string) {
    return this.svc.listLogs(matterId)
  }

  @Post('library/reseed')
  reseedLibrary() {
    return this.svc.reseedLibrary()
  }

  // ── Calibration agent routes (must come before :id) ───────────────────────

  @Get('calibrate/status')
  calibrationStatus() {
    return this.agent.getLastReport() ?? { message: 'No calibration run yet' }
  }

  @Post('calibrate/run')
  @HttpCode(202)
  async runCalibration(@Query('force') force?: string) {
    const forceRecalibrate = force === 'true'
    this.agent.calibrateAll(forceRecalibrate).catch(err =>
      console.error('Calibration run failed:', err),
    )
    return { message: 'Calibration started', forceRecalibrate }
  }

  @Post('calibrate/:id')
  calibrateOne(@Param('id') id: string) {
    return this.agent.calibrateOne(id)
  }

  // ── Tenant custom form upload ─────────────────────────────────────────────
  // POST /form-templates/upload
  // Tenant uploads their own PDF form. The system stores it, creates a
  // template record, and runs calibration to auto-map fields.
  // Tenants can then review/adjust the field mappings in the UI.

  @Post('upload')
  @UseInterceptors(FileInterceptor('file', {
    storage: diskStorage({
      destination: (_req, _file, cb) => {
        const dir = path.join(process.cwd(), 'uploads', 'form-templates', 'custom')
        fs.mkdirSync(dir, { recursive: true })
        cb(null, dir)
      },
      filename: (_req, _file, cb) => {
        cb(null, `${Date.now()}-${Math.random().toString(36).slice(2)}.pdf`)
      },
    }),
    fileFilter: (_req, file, cb) => {
      if (file.mimetype !== 'application/pdf' && !file.originalname.endsWith('.pdf')) {
        return cb(new BadRequestException('Only PDF files are accepted'), false)
      }
      cb(null, true)
    },
    limits: { fileSize: 20 * 1024 * 1024 },
  }))
  async uploadCustomForm(
    @UploadedFile() file: Express.Multer.File,
    @Body('name') name: string,
    @Body('practiceArea') practiceArea: string,
    @Body('description') description?: string,
    @Body('calibrate') calibrateParam?: string,
  ) {
    if (!file)          throw new BadRequestException('No file uploaded')
    if (!name?.trim())  throw new BadRequestException('name is required')

    const storageKey   = `custom/${file.filename}`
    const shouldCalibrate = calibrateParam !== 'false'

    const template = await this.svc.createCustomForm({
      name:             name.trim(),
      description,
      practiceArea:     practiceArea ?? 'universal',
      storageKey,
      originalFilename: file.originalname,
    })

    if (shouldCalibrate) {
      this.agent.calibrateOne(String((template as any)._id)).catch(err =>
        console.error(`Post-upload calibration failed for ${String((template as any)._id)}:`, err),
      )
    }

    return {
      id:          String((template as any)._id),
      name:        template.name,
      storageKey,
      sizeBytes:   file.size,
      calibrating: shouldCalibrate,
      message:     shouldCalibrate
        ? 'Form uploaded. Field mapping is running — check status at GET /form-templates/calibrate/status.'
        : 'Form uploaded. No calibration requested — add field mappings manually.',
    }
  }

  // ── Stock form upload (system admin) ──────────────────────────────────────
  // POST /form-templates/stock/:stockId/upload
  // Accepts a PDF, stores it as uploads/form-templates/stock/<stockId>.pdf,
  // updates the template record, and triggers calibration automatically.

  @Post('stock/:stockId/upload')
  @UseInterceptors(FileInterceptor('file', {
    storage: diskStorage({
      destination: (_req, _file, cb) => {
        const dir = path.join(process.cwd(), 'uploads', 'form-templates', 'stock')
        fs.mkdirSync(dir, { recursive: true })
        cb(null, dir)
      },
      filename: (req, _file, cb) => {
        const stockId = (req.params as any).stockId.replace(/[^a-zA-Z0-9_-]/g, '_')
        cb(null, `${stockId}.pdf`)
      },
    }),
    fileFilter: (_req, file, cb) => {
      if (file.mimetype !== 'application/pdf' && !file.originalname.endsWith('.pdf')) {
        return cb(new BadRequestException('Only PDF files are accepted'), false)
      }
      cb(null, true)
    },
    limits: { fileSize: 20 * 1024 * 1024 }, // 20 MB
  }))
  async uploadStockForm(
    @Param('stockId') stockId: string,
    @UploadedFile() file: Express.Multer.File,
  ) {
    if (!file) throw new BadRequestException('No file uploaded')

    const form = STOCK_FORMS.find(f => f.stockId === stockId)
    if (!form) throw new BadRequestException(`Unknown stockId: ${stockId}`)

    const safeId    = stockId.replace(/[^a-zA-Z0-9_-]/g, '_')
    const storageKey = `stock/${safeId}.pdf`

    // Update the template record with storageKey and sourceUrl (manual upload marker)
    await this.svc.updateStockFormStorage(stockId, storageKey)

    // Trigger calibration asynchronously
    const template = await this.svc.findByStockId(stockId)
    if (template) {
      this.agent.calibrateOne(String((template as any)._id)).catch(err =>
        console.error(`Post-upload calibration failed for ${stockId}:`, err),
      )
    }

    return {
      stockId,
      storageKey,
      sizeBytes: file.size,
      message: `Uploaded ${file.size} bytes. Calibration started.`,
    }
  }

  // ── Parameterised routes (must come last) ─────────────────────────────────

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.svc.findOne(id)
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() dto: UpdateFormTemplateDto) {
    return this.svc.update(id, dto)
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id') id: string) {
    return this.svc.remove(id)
  }

  @Get(':id/pdf')
  async servePdf(@Param('id') id: string, @Res() res: Response) {
    const { buffer, filename } = await this.svc.getRawPdf(id)
    res.set({
      'Content-Type':        'application/pdf',
      'Content-Disposition': `inline; filename="${filename}"`,
      'Content-Length':      buffer.length,
    })
    res.send(buffer)
  }

  @Post(':id/generate')
  async generate(
    @Param('id') id: string,
    @Query('matterId') matterId: string,
    @Res() res: Response,
  ) {
    const { buffer, filename } = await this.svc.generate(id, matterId)
    res.set({
      'Content-Type': 'application/pdf',
      'Content-Disposition': `attachment; filename="${filename}"`,
      'Content-Length': buffer.length,
    })
    res.send(buffer)
  }

  @Delete(':id/cached-pdf')
  @HttpCode(HttpStatus.NO_CONTENT)
  clearCachedPdf(@Param('id') id: string) {
    return this.svc.clearCachedPdf(id)
  }
}
