import {
  Controller,
  Get,
  Post,
  Patch,
  Delete,
  Body,
  Param,
  Query,
  ParseIntPipe,
  Req,
  Res,
  Logger,
  UploadedFile,
  UseInterceptors,
  Header,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import type { Request, Response } from 'express';
import * as fs from 'fs';
import * as nodePath from 'path';

import { DocumentsService, RequestContext } from './services/documents.service';
import { UploadDocumentDto } from './dto/upload-document.dto';
import { UpdateDocumentDto } from './dto/update-document.dto';
import { ListDocumentsQueryDto } from './dto/list-documents.dto';
import { UnlockDocumentDto } from './dto/unlock-document.dto';
import { ListAccessLogQueryDto } from './dto/list-access-log.dto';

@Controller('documents')
export class DocumentsController {
  private readonly logger = new Logger('DocumentsController');

  constructor(private readonly service: DocumentsService) {}

  // ── Helpers ──────────────────────────────────────────────────────────

  /** Same JWT extraction pattern as audit-requests. */
  private getCurrentUserId(req: Request): number {
    const user = (req as any).user;
    const id = user?.id ?? user?.userId ?? user?.sub;
    if (id === undefined || id === null) {
      throw new Error(
        'User id not found on request.user (JwtAuthGuard / strategy issue?)',
      );
    }
    return Number(id);
  }

  private getRequestContext(req: Request): RequestContext {
    const ipRaw =
      (req.headers['x-forwarded-for'] as string) ||
      req.socket?.remoteAddress ||
      req.ip ||
      null;
    // x-forwarded-for can be a comma-separated chain — take the first client IP.
    const ip = ipRaw ? String(ipRaw).split(',')[0].trim() : null;
    const user_agent = (req.headers['user-agent'] as string) || null;
    return { ip, user_agent };
  }

  // ═══════════════════════════════════════════════════════════════════
  //   ADMIN — UPLOAD / MANAGE
  // ═══════════════════════════════════════════════════════════════════

  /**
   * POST /documents
   * multipart/form-data with:
   *   - file: File
   *   - title, category, description
   *   - role_ids[]: 1,4,5,6
   *   - require_otp: 'true' | 'false'
   *   - allow_download: 'true' | 'false'
   *   - password: '...' (optional)
   *   - expiry_date: 'YYYY-MM-DD' (optional)
   */
  @Post()
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: (_req, _file, cb) => {
          const dir = nodePath.join(process.cwd(), 'uploads', 'documents');
          if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
          cb(null, dir);
        },
        filename: (_req, file, cb) => {
          const ts = Date.now();
          const safe = file.originalname.replace(/[^a-zA-Z0-9._-]/g, '_');
          cb(null, `${ts}_${safe}`);
        },
      }),
      limits: {
        fileSize: 500 * 1024 * 1024, // 500 MB — same as audit-requests
      },
    }),
  )
  async upload(
    @Body() dto: UploadDocumentDto,
    @UploadedFile() file: Express.Multer.File,
    @Req() req: Request,
  ) {
    const userId = this.getCurrentUserId(req);
    this.logger.log(
      `POST /documents — user=${userId}, title="${dto.title}", roles=[${dto.role_ids?.join(',')}]`,
    );
    return this.service.upload(dto, file, userId);
  }

  /** GET /documents/analytics — dashboard counts. */
  @Get('analytics')
  analytics(@Req() req: Request) {
    return this.service.getAnalytics(this.getCurrentUserId(req));
  }

  /**
   * GET /documents/roles/users?role_ids=3,4
   * Returns every user belonging to any of the given roles — used by the
   * upload modal to render the "who gets notified" checklist once the
   * admin picks role(s).
   */
  @Get('roles/users')
  usersForRoles(@Query('role_ids') roleIds: string, @Req() req: Request) {
    const ids = (roleIds || '')
      .split(',')
      .map((s) => Number(s.trim()))
      .filter((n) => Number.isInteger(n) && n > 0);
    return this.service.getUsersForRolesPublic(ids, this.getCurrentUserId(req));
  }

  /**
   * GET /documents
   * Admin → all docs.
   * Staff → only docs assigned to at least one of their roles, active + non-expired.
   */
  @Get()
  list(@Query() q: ListDocumentsQueryDto, @Req() req: Request) {
    return this.service.findAll(q, this.getCurrentUserId(req));
  }

  /** GET /documents/:id */
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number, @Req() req: Request) {
    return this.service.findOne(id, this.getCurrentUserId(req));
  }

  /** PATCH /documents/:id — metadata / security / role reassign. */
  @Patch(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateDocumentDto,
    @Req() req: Request,
  ) {
    const userId = this.getCurrentUserId(req);
    this.logger.log(`PATCH /documents/${id} — user=${userId}`);
    return this.service.update(id, dto, userId);
  }

  /** DELETE /documents/:id — soft-archive so the audit trail survives. */
  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number, @Req() req: Request) {
    const userId = this.getCurrentUserId(req);
    this.logger.log(`DELETE /documents/${id} — user=${userId}`);
    return this.service.remove(id, userId);
  }

  // ═══════════════════════════════════════════════════════════════════
  //   STAFF — OTP + UNLOCK + STREAM
  // ═══════════════════════════════════════════════════════════════════

  /** POST /documents/:id/request-otp — sends a 6-digit OTP to the current user's email. */
  @Post(':id/request-otp')
  requestOtp(@Param('id', ParseIntPipe) id: number, @Req() req: Request) {
    const userId = this.getCurrentUserId(req);
    const ctx = this.getRequestContext(req);
    this.logger.log(`POST /documents/${id}/request-otp — user=${userId}`);
    return this.service.requestOtp(id, userId, ctx);
  }

  /** POST /documents/:id/unlock — verifies password + OTP, returns a short-lived access token. */
  @Post(':id/unlock')
  unlock(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UnlockDocumentDto,
    @Req() req: Request,
  ) {
    const userId = this.getCurrentUserId(req);
    const ctx = this.getRequestContext(req);
    this.logger.log(`POST /documents/${id}/unlock — user=${userId}`);
    return this.service.unlock(id, dto, userId, ctx);
  }

  /**
   * GET /documents/:id/view?token=...
   * Streams the file inline (browser opens the viewer).
   */
  @Get(':id/view')
  @Header('Cache-Control', 'no-store')
  async view(
    @Param('id', ParseIntPipe) id: number,
    @Query('token') token: string,
    @Req() req: Request,
    @Res() res: Response,
  ) {
    const userId = this.getCurrentUserId(req);
    const ctx = this.getRequestContext(req);
    const { absPath, document } = await this.service.openForServe(
      id,
      token,
      'view',
      userId,
      ctx,
    );

    if (document.mime_type) res.setHeader('Content-Type', document.mime_type);
    res.setHeader(
      'Content-Disposition',
      `inline; filename="${encodeURIComponent(document.file_name)}"`,
    );
    // Discourage the browser from letting the user save via right-click.
    // (Best-effort — a determined user always can, that's why we log it.)
    res.setHeader('X-Content-Type-Options', 'nosniff');

    fs.createReadStream(absPath).pipe(res);
  }

  /**
   * GET /documents/:id/download?token=...
   * Streams the file as an attachment. 403 if allow_download = 0.
   */
  @Get(':id/download')
  @Header('Cache-Control', 'no-store')
  async download(
    @Param('id', ParseIntPipe) id: number,
    @Query('token') token: string,
    @Req() req: Request,
    @Res() res: Response,
  ) {
    const userId = this.getCurrentUserId(req);
    const ctx = this.getRequestContext(req);
    const { absPath, document } = await this.service.openForServe(
      id,
      token,
      'download',
      userId,
      ctx,
    );

    if (document.mime_type) res.setHeader('Content-Type', document.mime_type);
    res.setHeader(
      'Content-Disposition',
      `attachment; filename="${encodeURIComponent(document.file_name)}"`,
    );

    fs.createReadStream(absPath).pipe(res);
  }

  // ═══════════════════════════════════════════════════════════════════
  //   ADMIN — AUDIT TRAIL
  // ═══════════════════════════════════════════════════════════════════

  /** GET /documents/:id/access-log — paginated audit trail. */
  @Get(':id/access-log')
  getAccessLog(
    @Param('id', ParseIntPipe) id: number,
    @Query() q: ListAccessLogQueryDto,
    @Req() req: Request,
  ) {
    return this.service.getAccessLog(id, q, this.getCurrentUserId(req));
  }

  /** GET /documents/:id/access-log/export — CSV download. */
  @Get(':id/access-log/export')
  async exportAccessLog(
    @Param('id', ParseIntPipe) id: number,
    @Req() req: Request,
    @Res() res: Response,
  ) {
    const userId = this.getCurrentUserId(req);
    const csv = await this.service.exportAccessLogCsv(id, userId);
    res.setHeader('Content-Type', 'text/csv; charset=utf-8');
    res.setHeader(
      'Content-Disposition',
      `attachment; filename="document-${id}-access-log.csv"`,
    );
    res.send(csv);
  }
}
