import * as path from '@std/path';

export type TStorageBackend = 'filesystem' | 'memory';

export interface IStorageConfig {
  backend?: TStorageBackend;
  fsPath?: string;
}

/**
 * Key-value storage abstraction with filesystem and memory backends.
 * Keys must start with '/' and are normalized (no '..', no double slashes).
 */
export class StorageManager {
  private backend: TStorageBackend;
  private fsPath: string;
  private memoryStore: Map<string, string>;

  constructor(config: IStorageConfig = {}) {
    this.backend = config.backend ?? 'filesystem';
    this.fsPath = config.fsPath ?? './storage';
    this.memoryStore = new Map();
  }

  /**
   * Normalize and validate a storage key.
   */
  private normalizeKey(key: string): string {
    if (!key.startsWith('/')) {
      throw new Error(`Storage key must start with '/': ${key}`);
    }
    // Strip '..' segments and normalize double slashes
    const segments = key.split('/').filter((s) => s !== '' && s !== '..');
    return '/' + segments.join('/');
  }

  /**
   * Resolve a key to a filesystem path.
   */
  private keyToPath(key: string): string {
    const normalized = this.normalizeKey(key);
    return path.join(this.fsPath, ...normalized.split('/').filter(Boolean));
  }

  async get(key: string): Promise<string | null> {
    const normalized = this.normalizeKey(key);
    if (this.backend === 'memory') {
      return this.memoryStore.get(normalized) ?? null;
    }
    try {
      return await Deno.readTextFile(this.keyToPath(normalized));
    } catch (err) {
      if (err instanceof Deno.errors.NotFound) return null;
      throw err;
    }
  }

  async set(key: string, value: string): Promise<void> {
    const normalized = this.normalizeKey(key);
    if (this.backend === 'memory') {
      this.memoryStore.set(normalized, value);
      return;
    }
    const filePath = this.keyToPath(normalized);
    const dir = path.dirname(filePath);
    await Deno.mkdir(dir, { recursive: true });
    // Atomic write: write to temp then rename
    const tmpPath = filePath + '.tmp';
    await Deno.writeTextFile(tmpPath, value);
    await Deno.rename(tmpPath, filePath);
  }

  async delete(key: string): Promise<boolean> {
    const normalized = this.normalizeKey(key);
    if (this.backend === 'memory') {
      return this.memoryStore.delete(normalized);
    }
    try {
      await Deno.remove(this.keyToPath(normalized));
      return true;
    } catch (err) {
      if (err instanceof Deno.errors.NotFound) return false;
      throw err;
    }
  }

  async exists(key: string): Promise<boolean> {
    const normalized = this.normalizeKey(key);
    if (this.backend === 'memory') {
      return this.memoryStore.has(normalized);
    }
    try {
      await Deno.stat(this.keyToPath(normalized));
      return true;
    } catch (err) {
      if (err instanceof Deno.errors.NotFound) return false;
      throw err;
    }
  }

  /**
   * List keys under a given prefix.
   */
  async list(prefix: string): Promise<string[]> {
    const normalized = this.normalizeKey(prefix);
    if (this.backend === 'memory') {
      const keys: string[] = [];
      for (const key of this.memoryStore.keys()) {
        if (key.startsWith(normalized)) {
          keys.push(key);
        }
      }
      return keys.sort();
    }
    const dirPath = this.keyToPath(normalized);
    const keys: string[] = [];
    try {
      for await (const entry of Deno.readDir(dirPath)) {
        if (entry.isFile) {
          keys.push(normalized.replace(/\/$/, '') + '/' + entry.name);
        }
      }
    } catch (err) {
      if (err instanceof Deno.errors.NotFound) return [];
      throw err;
    }
    return keys.sort();
  }

  async getJSON<T>(key: string): Promise<T | null> {
    const raw = await this.get(key);
    if (raw === null) return null;
    return JSON.parse(raw) as T;
  }

  async setJSON(key: string, value: unknown): Promise<void> {
    await this.set(key, JSON.stringify(value, null, 2));
  }
}
