import * as plugins from '../plugins.js';
import type { IStorageManagerLike } from '@push.rocks/smartmta';

export class SmartMtaStorageManager implements IStorageManagerLike {
  private readonly resolvedRootDir: string;

  constructor(private rootDir: string) {
    this.resolvedRootDir = plugins.path.resolve(rootDir);
    plugins.fsUtils.ensureDirSync(this.resolvedRootDir);
  }

  private normalizeKey(key: string): string {
    return key.replace(/^\/+/, '').replace(/\\/g, '/');
  }

  private resolvePathForKey(key: string): string {
    const normalizedKey = this.normalizeKey(key);
    const resolvedPath = plugins.path.resolve(this.resolvedRootDir, normalizedKey);
    if (
      resolvedPath !== this.resolvedRootDir
      && !resolvedPath.startsWith(`${this.resolvedRootDir}${plugins.path.sep}`)
    ) {
      throw new Error(`Storage key escapes root directory: ${key}`);
    }
    return resolvedPath;
  }

  private toStorageKey(filePath: string): string {
    const relativePath = plugins.path.relative(this.resolvedRootDir, filePath).split(plugins.path.sep).join('/');
    return `/${relativePath}`;
  }

  public async get(key: string): Promise<string | null> {
    const filePath = this.resolvePathForKey(key);
    try {
      return await plugins.fs.promises.readFile(filePath, 'utf8');
    } catch (error: unknown) {
      if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
        return null;
      }
      throw error;
    }
  }

  public async set(key: string, value: string): Promise<void> {
    const filePath = this.resolvePathForKey(key);
    await plugins.fs.promises.mkdir(plugins.path.dirname(filePath), { recursive: true });
    await plugins.fs.promises.writeFile(filePath, value, 'utf8');
  }

  public async list(prefix: string): Promise<string[]> {
    const prefixPath = this.resolvePathForKey(prefix);
    try {
      const stat = await plugins.fs.promises.stat(prefixPath);
      if (stat.isFile()) {
        return [this.toStorageKey(prefixPath)];
      }
    } catch (error: unknown) {
      if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
        return [];
      }
      throw error;
    }

    const results: string[] = [];
    const walk = async (currentPath: string): Promise<void> => {
      const entries = await plugins.fs.promises.readdir(currentPath, { withFileTypes: true });
      for (const entry of entries) {
        const entryPath = plugins.path.join(currentPath, entry.name);
        if (entry.isDirectory()) {
          await walk(entryPath);
        } else if (entry.isFile()) {
          results.push(this.toStorageKey(entryPath));
        }
      }
    };

    await walk(prefixPath);
    return results.sort();
  }

  public async delete(key: string): Promise<void> {
    const targetPath = this.resolvePathForKey(key);
    try {
      const stat = await plugins.fs.promises.stat(targetPath);
      if (stat.isDirectory()) {
        await plugins.fs.promises.rm(targetPath, { recursive: true, force: true });
      } else {
        await plugins.fs.promises.unlink(targetPath);
      }
    } catch (error: unknown) {
      if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
        return;
      }
      throw error;
    }

    let currentDir = plugins.path.dirname(targetPath);
    while (currentDir.startsWith(this.resolvedRootDir) && currentDir !== this.resolvedRootDir) {
      const entries = await plugins.fs.promises.readdir(currentDir);
      if (entries.length > 0) {
        break;
      }
      await plugins.fs.promises.rmdir(currentDir);
      currentDir = plugins.path.dirname(currentDir);
    }
  }
}
