import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import os from 'node:os';

import {
  resolveAidenPaths,
  resolveAidenRoot,
  ensureAidenDirsExist,
} from '../../core/v4/paths';

const ORIGINAL_PLATFORM = process.platform;
const ORIGINAL_AIDEN_HOME = process.env.AIDEN_HOME;
const ORIGINAL_LOCALAPPDATA = process.env.LOCALAPPDATA;

function setPlatform(p: NodeJS.Platform): void {
  Object.defineProperty(process, 'platform', { value: p, configurable: true });
}

function restorePlatform(): void {
  Object.defineProperty(process, 'platform', {
    value: ORIGINAL_PLATFORM,
    configurable: true,
  });
}

beforeEach(() => {
  delete process.env.AIDEN_HOME;
});

afterEach(() => {
  restorePlatform();
  if (ORIGINAL_AIDEN_HOME === undefined) {
    delete process.env.AIDEN_HOME;
  } else {
    process.env.AIDEN_HOME = ORIGINAL_AIDEN_HOME;
  }
  if (ORIGINAL_LOCALAPPDATA === undefined) {
    delete process.env.LOCALAPPDATA;
  } else {
    process.env.LOCALAPPDATA = ORIGINAL_LOCALAPPDATA;
  }
});

describe('resolveAidenPaths', () => {
  it('1. resolves Windows root to %LOCALAPPDATA%\\aiden', () => {
    setPlatform('win32');
    process.env.LOCALAPPDATA = 'C:\\Users\\test\\AppData\\Local';
    const p = resolveAidenPaths();
    expect(p.root).toBe(path.join('C:\\Users\\test\\AppData\\Local', 'aiden'));
    expect(p.authJson).toBe(path.join(p.root, 'auth.json'));
    expect(p.sessionsDb).toBe(path.join(p.root, 'sessions.db'));
  });

  it('2. resolves Linux root to ~/.aiden', () => {
    setPlatform('linux');
    const p = resolveAidenPaths();
    expect(p.root).toBe(path.join(os.homedir(), '.aiden'));
  });

  it('3. resolves macOS root to ~/Library/Application Support/aiden', () => {
    setPlatform('darwin');
    const p = resolveAidenPaths();
    expect(p.root).toBe(
      path.join(os.homedir(), 'Library', 'Application Support', 'aiden'),
    );
  });

  it('4. AIDEN_HOME env var overrides platform default', () => {
    setPlatform('linux');
    process.env.AIDEN_HOME = '/custom/aiden-home';
    const p = resolveAidenPaths();
    expect(p.root).toBe(path.resolve('/custom/aiden-home'));
    expect(p.configYaml).toBe(path.join(p.root, 'config.yaml'));
  });

  it('5. rootOverride wins over AIDEN_HOME and platform', () => {
    setPlatform('win32');
    process.env.AIDEN_HOME = '/should/be/ignored';
    process.env.LOCALAPPDATA = 'C:\\Users\\test\\AppData\\Local';
    const root = '/explicit/root';
    const p = resolveAidenPaths({ rootOverride: root });
    expect(p.root).toBe(path.resolve(root));
    expect(resolveAidenRoot({ rootOverride: root })).toBe(path.resolve(root));
  });

  it('6. exposes every documented subpath under root', () => {
    setPlatform('linux');
    const root = '/tmp/aiden-paths-doc-test';
    const p = resolveAidenPaths({ rootOverride: root });
    expect(p.sessionsDb).toBe(path.join(p.root, 'sessions.db'));
    expect(p.authJson).toBe(path.join(p.root, 'auth.json'));
    expect(p.configYaml).toBe(path.join(p.root, 'config.yaml'));
    expect(p.envFile).toBe(path.join(p.root, '.env'));
    expect(p.soulMd).toBe(path.join(p.root, 'SOUL.md'));
    expect(p.memoryMd).toBe(path.join(p.root, 'memories', 'MEMORY.md'));
    expect(p.userMd).toBe(path.join(p.root, 'memories', 'USER.md'));
    expect(p.skillsDir).toBe(path.join(p.root, 'skills'));
    expect(p.sessionsDir).toBe(path.join(p.root, 'sessions'));
    expect(p.pluginsDir).toBe(path.join(p.root, 'plugins'));
    expect(p.logsDir).toBe(path.join(p.root, 'logs'));
    expect(p.bundledManifest).toBe(path.join(p.root, '.bundled_manifest'));
  });

  it('7. Windows falls back to ~/AppData/Local when LOCALAPPDATA is unset', () => {
    setPlatform('win32');
    delete process.env.LOCALAPPDATA;
    const p = resolveAidenPaths();
    expect(p.root).toBe(path.join(os.homedir(), 'AppData', 'Local', 'aiden'));
  });

  it('8. ensureAidenDirsExist creates root + every required subdir', async () => {
    const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'aiden-paths-'));
    try {
      const p = resolveAidenPaths({ rootOverride: tmp });
      await ensureAidenDirsExist(p);
      for (const dir of [
        p.root,
        p.skillsDir,
        p.sessionsDir,
        p.pluginsDir,
        p.logsDir,
        path.dirname(p.memoryMd),
      ]) {
        const stat = await fs.stat(dir);
        expect(stat.isDirectory()).toBe(true);
      }
      // Files are NOT pre-created.
      await expect(fs.stat(p.sessionsDb)).rejects.toThrow();
      await expect(fs.stat(p.configYaml)).rejects.toThrow();
    } finally {
      await fs.rm(tmp, { recursive: true, force: true });
    }
  });

  it('9. ensureAidenDirsExist is idempotent', async () => {
    const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'aiden-paths-'));
    try {
      const p = resolveAidenPaths({ rootOverride: tmp });
      await ensureAidenDirsExist(p);
      // Second invocation should not throw.
      await ensureAidenDirsExist(p);
      const stat = await fs.stat(p.skillsDir);
      expect(stat.isDirectory()).toBe(true);
    } finally {
      await fs.rm(tmp, { recursive: true, force: true });
    }
  });
});
