/**
 * LocalStorageProvider
 *
 * Local filesystem storage implementation following the Strategy Pattern.
 * Implements BaseStorageProvider interface for local disk operations.
 */

const fs = require('fs');
const path = require('path');
const { pipeline } = require('stream/promises');
const BaseStorageProvider = require('./BaseStorageProvider');

/**
 * Ensure directory exists for a file path
 */
const ensureDirectoryExistence = (filePath) => {
  const dirname = path.dirname(filePath);

  if (fs.existsSync(dirname)) {
    return true;
  }

  ensureDirectoryExistence(dirname);
  fs.mkdirSync(dirname);
};

class LocalStorageProvider extends BaseStorageProvider {
  /**
   * @param {Object} options
   * @param {string} options.basePath - Base directory for file storage
   */
  constructor(options = {}) {
    super();
    this.basePath = options.basePath || './uploads';

    // Ensure base path exists
    if (!fs.existsSync(this.basePath)) {
      fs.mkdirSync(this.basePath, { recursive: true });
    }
  }

  static get providerName() {
    return 'local';
  }

  /**
   * Build full file path
   */
  buildPath(key) {
    const cleanKey = (key || '').replace(/^\/+/, '');
    return path.join(this.basePath, cleanKey);
  }

  /**
   * Upload a file to local storage
   * @param {string} key - Storage key/path
   * @param {Buffer|ReadableStream} data - File data
   * @param {Object} _options - Upload options (not used for local)
   * @returns {Promise<{ key: string }>}
   */
  async upload(key, data, _options = {}) {
    const filePath = this.buildPath(key);
    ensureDirectoryExistence(filePath);

    if (Buffer.isBuffer(data)) {
      fs.writeFileSync(filePath, data);
    } else if (data && typeof data.pipe === 'function') {
      // Handle stream
      const writeStream = fs.createWriteStream(filePath);
      await pipeline(data, writeStream);
    } else {
      throw new Error('Data must be a Buffer or ReadableStream');
    }

    return { key };
  }

  /**
   * Download a file from local storage
   * @param {string} key - Storage key/path
   * @returns {Promise<{ body: ReadableStream, contentType?: string }>}
   */
  async download(key) {
    const filePath = this.buildPath(key);

    if (!fs.existsSync(filePath)) {
      throw new Error(`File not found: ${key}`);
    }

    const body = fs.createReadStream(filePath);

    // Try to determine content type from extension
    const ext = path.extname(filePath).toLowerCase();
    const contentType = this.getContentType(ext);

    return { body, contentType };
  }

  /**
   * Delete a file from local storage
   * @param {string} key - Storage key/path
   * @returns {Promise<void>}
   */
  async delete(key) {
    const filePath = this.buildPath(key);

    if (fs.existsSync(filePath)) {
      fs.unlinkSync(filePath);
    }
  }

  /**
   * Delete multiple files from local storage
   * @param {string[]} keys - Array of keys to delete
   * @returns {Promise<void>}
   */
  async deleteMany(keys) {
    for (const key of keys) {
      await this.delete(key);
    }
  }

  /**
   * Check if a file exists in local storage
   * @param {string} key - Storage key/path
   * @returns {Promise<boolean>}
   */
  async exists(key) {
    const filePath = this.buildPath(key);
    return fs.existsSync(filePath);
  }

  /**
   * List files with a given prefix
   * @param {string} prefix - Key prefix (directory path)
   * @returns {Promise<string[]>} Array of keys
   */
  async list(prefix) {
    const dirPath = this.buildPath(prefix);

    if (!fs.existsSync(dirPath)) {
      return [];
    }

    const stat = fs.statSync(dirPath);
    if (!stat.isDirectory()) {
      return [prefix];
    }

    const files = [];
    const readDir = (dir, relativePath = '') => {
      const entries = fs.readdirSync(dir, { withFileTypes: true });

      for (const entry of entries) {
        const entryPath = path.join(relativePath, entry.name);
        const fullPath = path.join(dir, entry.name);

        if (entry.isDirectory()) {
          readDir(fullPath, entryPath);
        } else {
          files.push(path.join(prefix, entryPath));
        }
      }
    };

    readDir(dirPath);
    return files;
  }

  /**
   * Get a signed URL for direct access (not supported for local storage)
   * For local storage, return the file path that can be served by express.static
   * @param {string} key - Storage key/path
   * @param {number} _expiresIn - Expiration time (ignored for local)
   * @returns {Promise<string|null>}
   */
  async getSignedUrl(key, _expiresIn) {
    // Local storage doesn't support signed URLs
    // Return the relative path that can be served by a static file server
    return `/uploads/${key}`;
  }

  /**
   * Get base path
   * @returns {string}
   */
  getBasePath() {
    return this.basePath;
  }

  /**
   * Get content type from file extension
   * @param {string} ext - File extension
   * @returns {string}
   */
  getContentType(ext) {
    const types = {
      '.jpg': 'image/jpeg',
      '.jpeg': 'image/jpeg',
      '.png': 'image/png',
      '.gif': 'image/gif',
      '.webp': 'image/webp',
      '.svg': 'image/svg+xml',
      '.mp4': 'video/mp4',
      '.webm': 'video/webm',
      '.mp3': 'audio/mpeg',
      '.wav': 'audio/wav',
      '.ogg': 'audio/ogg',
      '.pdf': 'application/pdf',
      '.json': 'application/json',
      '.txt': 'text/plain',
      '.html': 'text/html',
      '.css': 'text/css',
      '.js': 'application/javascript',
    };

    return types[ext] || 'application/octet-stream';
  }
}

module.exports = LocalStorageProvider;
