import { Injectable, Optional } from '@nestjs/common';
import { promises as fsp, existsSync } from 'node:fs';
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
import { randomBytes } from 'node:crypto';
import { isAllowedPanExtension } from './file-sniff';

const SAFE_FILENAME = /^(?!^\.\.?$)[a-zA-Z0-9._-]+$/;
const DOC_FOLDER = 'doc';

/**
 * Private on-disk store for identity documents (PAN cards).
 *
 * Not mounted on `/api/uploads`. Callers must stream files through an
 * authenticated controller after a tenant-scoped booking lookup — never
 * by a client-supplied path.
 */
@Injectable()
export class PrivateDocumentStorage {
  private readonly root: string;
  private readonly docDir: string;

  constructor(@Optional() root?: string) {
    this.root = root ? resolve(root) : resolvePrivateUploadsRoot();
    this.docDir = join(this.root, DOC_FOLDER);
  }

  async save(extension: string, body: Buffer): Promise<string> {
    const ext = (extension || '').toLowerCase();
    if (!isAllowedPanExtension(ext)) {
      throw new Error('Unsupported file extension');
    }
    const filename = `${Date.now()}-${randomBytes(8).toString('hex')}${ext}`;
    const abs = this.absPath(filename);
    await fsp.mkdir(dirname(abs), { recursive: true });
    await fsp.writeFile(abs, body);
    return filename;
  }

  async read(filename: string): Promise<Buffer> {
    return fsp.readFile(this.absPath(filename));
  }

  async exists(filename: string): Promise<boolean> {
    return existsSync(this.absPath(filename));
  }

  async delete(filename: string): Promise<void> {
    await fsp.rm(this.absPath(filename), { force: true });
  }

  private absPath(filename: string): string {
    if (!SAFE_FILENAME.test(filename) || filename.includes('..') || filename.includes(sep)) {
      throw new Error('Unsafe private document filename');
    }
    const abs = resolve(this.docDir, filename);
    if (abs !== this.docDir && !abs.startsWith(this.docDir + sep)) {
      throw new Error('Unsafe private document filename — path escapes store');
    }
    return abs;
  }
}

export function resolvePrivateUploadsRoot(localDir = 'privateUploads'): string {
  const cwd = process.cwd();
  const abs = resolve(cwd, localDir);
  const rel = relative(cwd, abs);
  if (!rel || rel.startsWith('..') || isAbsolute(rel)) {
    throw new Error(
      'privateUploads must be a directory inside the application working directory',
    );
  }
  return abs;
}
