import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { promises as fs } from 'fs';
import * as path from 'path';
import { createReadStream } from 'fs';
import { Readable } from 'stream';

export type AuditDocType =
  | 'stage1_report'
  | 'stage2_report'
  | 'attendance'
  | 'nc_form'
  | 'support_docs';

// storage/auditReport/<folder>/<rowId>/<ts>_<filename>
const FOLDER_BY_TYPE: Record<AuditDocType, string> = {
  stage1_report: 'stage1',
  stage2_report: 'stage2',
  attendance: 'attendance',
  nc_form: 'nc_form',
  support_docs: 'support_docs',
};

const STORAGE_ROOT =
  process.env.AUDIT_REPORT_ROOT ||
  '/var/www/scheme_certiifcation/backend/storage/auditReport';

const NEW_PATH_PREFIX = 'new:';

const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
const ALLOWED_MIME_TYPES = new Set([
  'application/pdf',
  'image/png',
  'image/jpeg',
  'image/jpg',
  'image/webp',
  'application/msword',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'application/vnd.ms-excel',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'application/zip',
  'application/x-zip-compressed',
]);

@Injectable()
export class AuditReportStorageService {
  private readonly logger = new Logger(AuditReportStorageService.name);

  isNewPath(dbPath: string | null | undefined): boolean {
    return !!dbPath && dbPath.startsWith(NEW_PATH_PREFIX);
  }

  resolveAbsolutePath(dbPath: string): string {
    if (!this.isNewPath(dbPath)) {
      // not a managed path — return as-is for legacy/raw values
      return dbPath;
    }
    const relative = dbPath.slice(NEW_PATH_PREFIX.length);
    return path.join(STORAGE_ROOT, relative);
  }

  async saveReport(
    docType: AuditDocType,
    rowId: number,
    file: {
      originalname: string;
      mimetype: string;
      buffer: Buffer;
      size: number;
    },
  ): Promise<{ dbPath: string; absolutePath: string; size: number }> {
    this.validate(file);

    const folder = FOLDER_BY_TYPE[docType];
    if (!folder) throw new Error(`Unknown audit document type: ${docType}`);

    const timestamp = Math.floor(Date.now() / 1000);
    const safeName = this.sanitizeFilename(file.originalname);
    const filename = `${timestamp}_${safeName}`;

    const relativeDir = `${folder}/${rowId}`;
    const absoluteDir = path.join(STORAGE_ROOT, relativeDir);
    await fs.mkdir(absoluteDir, { recursive: true });

    const absolutePath = path.join(absoluteDir, filename);
    await fs.writeFile(absolutePath, file.buffer);
    await fs.chmod(absolutePath, 0o664).catch(() => null);

    const dbPath = `${NEW_PATH_PREFIX}${relativeDir}/${filename}`;
    this.logger.log(
      `Saved ${docType}: ${absolutePath} (size=${file.size}) → ${dbPath}`,
    );

    return { dbPath, absolutePath, size: file.size };
  }

  async openFile(dbPath: string): Promise<{
    stream: Readable;
    size: number;
    mimeType: string;
    filename: string;
  }> {
    const absolutePath = this.resolveAbsolutePath(dbPath);

    let stat;
    try {
      stat = await fs.stat(absolutePath);
    } catch {
      this.logger.warn(`File not found: ${absolutePath} (DB: ${dbPath})`);
      throw new NotFoundException('File not found on disk');
    }
    if (!stat.isFile()) throw new NotFoundException('Path is not a file');

    const filename = path.basename(absolutePath);
    return {
      stream: createReadStream(absolutePath),
      size: stat.size,
      mimeType: this.guessMimeType(filename),
      filename,
    };
  }

  async deleteNewFile(dbPath: string): Promise<boolean> {
    if (!this.isNewPath(dbPath)) {
      this.logger.warn(`Refused to delete non-managed file: ${dbPath}`);
      return false;
    }
    const absolutePath = this.resolveAbsolutePath(dbPath);
    try {
      await fs.unlink(absolutePath);
      this.logger.log(`Deleted: ${absolutePath}`);
      return true;
    } catch (err: any) {
      if (err.code === 'ENOENT') return true;
      this.logger.error(`Delete failed ${absolutePath}: ${err.message}`);
      throw err;
    }
  }

  private validate(file: { originalname: string; mimetype: string; size: number }): void {
    if (!file.originalname?.trim()) throw new Error('Filename is required');
    if (file.size === 0) throw new Error('Empty file not allowed');
    if (file.size > MAX_FILE_SIZE) {
      throw new Error(
        `File too large (${(file.size / 1024 / 1024).toFixed(1)} MB). Max ${MAX_FILE_SIZE / 1024 / 1024} MB.`,
      );
    }
    if (!ALLOWED_MIME_TYPES.has(file.mimetype)) {
      throw new Error(`File type not allowed: ${file.mimetype}`);
    }
  }

  private sanitizeFilename(name: string): string {
    return (
      name
        .replace(/^.*[\\/]/, '')
        .replace(/[\x00-\x1f]/g, '')
        .replace(/[<>:"|?*]/g, '_')
        .trim()
        .slice(0, 200) || 'file'
    );
  }

  private guessMimeType(filename: string): string {
    const ext = filename.toLowerCase().split('.').pop() || '';
    const map: Record<string, string> = {
      pdf: 'application/pdf',
      png: 'image/png',
      jpg: 'image/jpeg',
      jpeg: 'image/jpeg',
      webp: 'image/webp',
      doc: 'application/msword',
      docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
      xls: 'application/vnd.ms-excel',
      xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      zip: 'application/zip',
      txt: 'text/plain',
    };
    return map[ext] || 'application/octet-stream';
  }
}