import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import SharedManager from '../../src/management/shared-manager';
import InstanceManager from '../../src/management/instance-manager';
import type { AccountContextPolicy } from '../../src/auth/account-context';

function getTestCcsDir(): string {
  if (!process.env.CCS_HOME) {
    throw new Error('CCS_HOME must be set in tests');
  }
  return path.join(path.resolve(process.env.CCS_HOME), '.ccs');
}

function createDirectorySymlink(targetPath: string, linkPath: string): void {
  const symlinkType: 'dir' | 'junction' = process.platform === 'win32' ? 'junction' : 'dir';
  const linkTarget = process.platform === 'win32' ? path.resolve(targetPath) : targetPath;
  fs.symlinkSync(linkTarget, linkPath, symlinkType);
}

describe('SharedManager context policy', () => {
  let tempRoot = '';
  let originalHome: string | undefined;
  let originalCcsHome: string | undefined;
  let originalCcsDir: string | undefined;

  beforeEach(() => {
    tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-context-policy-test-'));
    originalHome = process.env.HOME;
    originalCcsHome = process.env.CCS_HOME;
    originalCcsDir = process.env.CCS_DIR;

    const isolatedHome = path.join(tempRoot, 'home');
    fs.mkdirSync(isolatedHome, { recursive: true });
    process.env.HOME = isolatedHome;
    process.env.CCS_HOME = tempRoot;
    delete process.env.CCS_DIR;
  });

  afterEach(() => {
    if (originalHome !== undefined) process.env.HOME = originalHome;
    else delete process.env.HOME;

    if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome;
    else delete process.env.CCS_HOME;

    if (originalCcsDir !== undefined) process.env.CCS_DIR = originalCcsDir;
    else delete process.env.CCS_DIR;

    if (tempRoot && fs.existsSync(tempRoot)) {
      fs.rmSync(tempRoot, { recursive: true, force: true });
    }
  });

  async function applyPolicy(policy: AccountContextPolicy): Promise<{ instancePath: string; ccsDir: string }> {
    const ccsDir = getTestCcsDir();
    const instancePath = path.join(ccsDir, 'instances', 'work');
    fs.mkdirSync(instancePath, { recursive: true });

    const manager = new SharedManager();
    await manager.syncProjectContext(instancePath, policy);

    return { instancePath, ccsDir };
  }

  async function applyPolicyWithContinuity(
    policy: AccountContextPolicy
  ): Promise<{ instancePath: string; ccsDir: string }> {
    const ccsDir = getTestCcsDir();
    const instancePath = path.join(ccsDir, 'instances', 'work');
    fs.mkdirSync(instancePath, { recursive: true });

    const manager = new SharedManager();
    await manager.syncProjectContext(instancePath, policy);
    await manager.syncAdvancedContinuityArtifacts(instancePath, policy);

    return { instancePath, ccsDir };
  }

  it('keeps projects isolated by default', async () => {
    const { instancePath } = await applyPolicy({ mode: 'isolated' });
    const projectsPath = path.join(instancePath, 'projects');

    expect(fs.existsSync(projectsPath)).toBe(true);
    expect(fs.lstatSync(projectsPath).isDirectory()).toBe(true);
  });

  it('migrates local projects into shared context group', async () => {
    const ccsDir = getTestCcsDir();
    const instancePath = path.join(ccsDir, 'instances', 'work');
    const localProjectsPath = path.join(instancePath, 'projects');
    const localFile = path.join(localProjectsPath, '-tmp-project', 'notes.md');

    fs.mkdirSync(path.dirname(localFile), { recursive: true });
    fs.writeFileSync(localFile, 'local context', 'utf8');

    const manager = new SharedManager();
    await manager.syncProjectContext(instancePath, { mode: 'shared', group: 'sprint-a' });

    const linkStats = fs.lstatSync(localProjectsPath);
    expect(linkStats.isSymbolicLink()).toBe(true);

    const sharedFile = path.join(
      ccsDir,
      'shared',
      'context-groups',
      'sprint-a',
      'projects',
      '-tmp-project',
      'notes.md'
    );
    expect(fs.existsSync(sharedFile)).toBe(true);
    expect(fs.readFileSync(sharedFile, 'utf8')).toBe('local context');
  });

  it('switches from shared mode back to isolated without data loss', async () => {
    const { instancePath, ccsDir } = await applyPolicy({ mode: 'shared', group: 'sprint-a' });
    const sharedFile = path.join(
      ccsDir,
      'shared',
      'context-groups',
      'sprint-a',
      'projects',
      '-tmp-project',
      'history.md'
    );

    fs.mkdirSync(path.dirname(sharedFile), { recursive: true });
    fs.writeFileSync(sharedFile, 'shared history', 'utf8');

    const manager = new SharedManager();
    await manager.syncProjectContext(instancePath, { mode: 'isolated' });

    const projectsPath = path.join(instancePath, 'projects');
    const projectFile = path.join(projectsPath, '-tmp-project', 'history.md');

    expect(fs.lstatSync(projectsPath).isDirectory()).toBe(true);
    expect(fs.existsSync(projectFile)).toBe(true);
    expect(fs.readFileSync(projectFile, 'utf8')).toBe('shared history');
  });

  it('serializes concurrent context sync for the same profile', async () => {
    const instanceMgr = new InstanceManager();
    const jobs = Array.from({ length: 6 }, () =>
      instanceMgr.ensureInstance('work', { mode: 'shared', group: 'sprint-a' })
    );

    await Promise.all(jobs);

    const ccsDir = getTestCcsDir();
    const projectsPath = path.join(ccsDir, 'instances', 'work', 'projects');
    const stats = fs.lstatSync(projectsPath);

    expect(stats.isDirectory() || stats.isSymbolicLink()).toBe(true);
  });

  it('links advanced continuity artifacts for shared deeper mode', async () => {
    const { instancePath, ccsDir } = await applyPolicyWithContinuity({
      mode: 'shared',
      group: 'sprint-a',
      continuityMode: 'deeper',
    });

    const artifactPath = path.join(instancePath, 'session-env');
    const targetFile = path.join(
      ccsDir,
      'shared',
      'context-groups',
      'sprint-a',
      'continuity',
      'session-env',
      'session.json'
    );

    fs.mkdirSync(path.dirname(targetFile), { recursive: true });
    fs.writeFileSync(targetFile, '{"id":"shared"}', 'utf8');

    expect(fs.lstatSync(artifactPath).isSymbolicLink()).toBe(true);
    expect(fs.readFileSync(path.join(artifactPath, 'session.json'), 'utf8')).toContain('shared');
  });

  it('detaches advanced continuity artifacts when moving from deeper to standard shared mode', async () => {
    const { instancePath, ccsDir } = await applyPolicyWithContinuity({
      mode: 'shared',
      group: 'sprint-a',
      continuityMode: 'deeper',
    });

    const sharedTodo = path.join(
      ccsDir,
      'shared',
      'context-groups',
      'sprint-a',
      'continuity',
      'todos',
      'todo.md'
    );
    fs.mkdirSync(path.dirname(sharedTodo), { recursive: true });
    fs.writeFileSync(sharedTodo, '- shared todo', 'utf8');

    const manager = new SharedManager();
    await manager.syncAdvancedContinuityArtifacts(instancePath, {
      mode: 'shared',
      group: 'sprint-a',
      continuityMode: 'standard',
    });

    const localTodoDir = path.join(instancePath, 'todos');
    expect(fs.lstatSync(localTodoDir).isDirectory()).toBe(true);
    expect(fs.readFileSync(path.join(localTodoDir, 'todo.md'), 'utf8')).toContain('shared todo');
  });

  it('skips merge when projects symlink target is outside canonical CCS roots', async () => {
    const ccsDir = getTestCcsDir();
    const instancePath = path.join(ccsDir, 'instances', 'work');
    const projectsPath = path.join(instancePath, 'projects');
    const unsafeProjectsPath = path.join(ccsDir, 'shared', 'context-groups-evil', 'projects');
    const unsafeFile = path.join(unsafeProjectsPath, '-tmp-project', 'notes.md');

    fs.mkdirSync(path.dirname(unsafeFile), { recursive: true });
    fs.writeFileSync(unsafeFile, 'unsafe source', 'utf8');
    fs.mkdirSync(instancePath, { recursive: true });
    createDirectorySymlink(unsafeProjectsPath, projectsPath);

    const manager = new SharedManager();
    await manager.syncProjectContext(instancePath, { mode: 'isolated' });

    expect(fs.lstatSync(projectsPath).isDirectory()).toBe(true);
    expect(fs.existsSync(path.join(projectsPath, '-tmp-project', 'notes.md'))).toBe(false);
  });

  it('does not detach project memory symlink from lookalike shared path prefixes', async () => {
    const ccsDir = getTestCcsDir();
    const instancePath = path.join(ccsDir, 'instances', 'work');
    const projectPath = path.join(instancePath, 'projects', '-tmp-project');
    const memoryPath = path.join(projectPath, 'memory');
    const unsafeMemoryTarget = path.join(ccsDir, 'shared', 'memory-evil', '-tmp-project');
    const unsafeMemoryFile = path.join(unsafeMemoryTarget, 'MEMORY.md');

    fs.mkdirSync(path.dirname(unsafeMemoryFile), { recursive: true });
    fs.writeFileSync(unsafeMemoryFile, 'unsafe memory', 'utf8');
    fs.mkdirSync(projectPath, { recursive: true });
    createDirectorySymlink(unsafeMemoryTarget, memoryPath);

    const manager = new SharedManager();
    await manager.syncProjectContext(instancePath, { mode: 'isolated' });

    expect(fs.lstatSync(memoryPath).isSymbolicLink()).toBe(true);
  });
});
