/**
 * RepoMap 测试
 */

import { describe, it, expect, beforeAll } from 'vitest';
import * as path from 'path';
import * as fs from 'fs/promises';
import * as os from 'os';
import { createRepoMap } from '../../src/repomap/repomap.js';
import { TagExtractor } from '../../src/repomap/tags/extractor.js';
import { Graph, pagerank, distributeRanksToDefinitions } from '../../src/repomap/ranking/index.js';
import { DiskCache } from '../../src/repomap/cache/disk-cache.js';

describe('Graph', () => {
  it('should add edges and track nodes', () => {
    const graph = new Graph();

    graph.addEdge({ from: 'a.ts', to: 'b.ts', weight: 1, ident: 'foo' });
    graph.addEdge({ from: 'a.ts', to: 'c.ts', weight: 2, ident: 'bar' });
    graph.addEdge({ from: 'b.ts', to: 'c.ts', weight: 1, ident: 'baz' });

    const nodes = graph.getNodes();
    expect(nodes).toContain('a.ts');
    expect(nodes).toContain('b.ts');
    expect(nodes).toContain('c.ts');
    expect(nodes.length).toBe(3);
  });

  it('should track in and out edges correctly', () => {
    const graph = new Graph();

    graph.addEdge({ from: 'a.ts', to: 'b.ts', weight: 1, ident: 'foo' });
    graph.addEdge({ from: 'a.ts', to: 'c.ts', weight: 2, ident: 'bar' });

    const outEdges = graph.getOutEdges('a.ts');
    expect(outEdges.length).toBe(2);

    const inEdges = graph.getInEdges('b.ts');
    expect(inEdges.length).toBe(1);
    expect(inEdges[0].from).toBe('a.ts');
  });

  it('should calculate out degree correctly', () => {
    const graph = new Graph();

    graph.addEdge({ from: 'a.ts', to: 'b.ts', weight: 1, ident: 'foo' });
    graph.addEdge({ from: 'a.ts', to: 'c.ts', weight: 2, ident: 'bar' });

    expect(graph.getOutDegree('a.ts')).toBe(3); // sum of weights
    expect(graph.getOutDegree('b.ts')).toBe(0);
  });
});

describe('PageRank', () => {
  it('should compute ranks for a simple graph', () => {
    const graph = new Graph();

    // 创建一个简单的图: a -> b -> c
    graph.addEdge({ from: 'a.ts', to: 'b.ts', weight: 1, ident: 'foo' });
    graph.addEdge({ from: 'b.ts', to: 'c.ts', weight: 1, ident: 'bar' });

    const ranks = pagerank(graph);

    expect(ranks.size).toBe(3);
    expect(ranks.get('a.ts')).toBeGreaterThan(0);
    expect(ranks.get('b.ts')).toBeGreaterThan(0);
    expect(ranks.get('c.ts')).toBeGreaterThan(0);
  });

  it('should respect personalization vector', () => {
    const graph = new Graph();

    graph.addEdge({ from: 'a.ts', to: 'b.ts', weight: 1, ident: 'foo' });
    graph.addEdge({ from: 'b.ts', to: 'c.ts', weight: 1, ident: 'bar' });
    graph.addEdge({ from: 'c.ts', to: 'a.ts', weight: 1, ident: 'baz' });

    // 高度偏好 a.ts
    const personalization = new Map<string, number>();
    personalization.set('a.ts', 100);
    personalization.set('b.ts', 1);
    personalization.set('c.ts', 1);

    const ranks = pagerank(graph, { personalization });

    // a.ts 应该有较高的 rank
    expect(ranks.get('a.ts')!).toBeGreaterThan(ranks.get('b.ts')!);
  });

  it('should distribute ranks to definitions', () => {
    const graph = new Graph();

    graph.addEdge({ from: 'a.ts', to: 'b.ts', weight: 1, ident: 'foo' });
    graph.addEdge({ from: 'a.ts', to: 'b.ts', weight: 2, ident: 'bar' });

    const nodeRanks = pagerank(graph);
    const defRanks = distributeRanksToDefinitions(graph, nodeRanks);

    expect(defRanks.has('b.ts:foo')).toBe(true);
    expect(defRanks.has('b.ts:bar')).toBe(true);
  });
});

describe('DiskCache', () => {
  let cacheDir: string;
  let cache: DiskCache<{ value: number }>;

  beforeAll(async () => {
    cacheDir = path.join(os.tmpdir(), `repomap-test-${Date.now()}`);
    cache = new DiskCache(cacheDir);
  });

  it('should set and get values', async () => {
    await cache.set('key1', { value: 42 });
    const result = await cache.get('key1');

    expect(result).not.toBeNull();
    expect(result?.value).toBe(42);
  });

  it('should return null for missing keys', async () => {
    const result = await cache.get('nonexistent');
    expect(result).toBeNull();
  });

  it('should delete values', async () => {
    await cache.set('key2', { value: 100 });
    await cache.delete('key2');

    const result = await cache.get('key2');
    expect(result).toBeNull();
  });

  it('should check existence with has', async () => {
    await cache.set('key3', { value: 200 });

    expect(await cache.has('key3')).toBe(true);
    expect(await cache.has('nonexistent2')).toBe(false);
  });
});

describe('TagExtractor', () => {
  let tempDir: string;

  beforeAll(async () => {
    tempDir = path.join(os.tmpdir(), `repomap-extractor-test-${Date.now()}`);
    await fs.mkdir(tempDir, { recursive: true });
  });

  it('should extract tags from TypeScript code using regex fallback', async () => {
    const testFile = path.join(tempDir, 'test.ts');
    const code = `
export function greet(name: string): string {
  return \`Hello, \${name}!\`;
}

export class Greeter {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }

  greet(): string {
    return \`Hello, \${this.name}!\`;
  }
}

export interface IGreeter {
  greet(): string;
}
`;

    await fs.writeFile(testFile, code);

    const extractor = new TagExtractor();
    const tags = await extractor.getTags(testFile, 'test.ts');

    // 检查是否提取到了一些 tags
    expect(tags.length).toBeGreaterThan(0);

    // 检查是否有定义
    const defs = tags.filter((t) => t.kind === 'def');
    expect(defs.length).toBeGreaterThan(0);

    // 检查是否包含函数和类
    const defNames = defs.map((t) => t.name);
    expect(defNames).toContain('greet');
    expect(defNames).toContain('Greeter');
  });

  it('should extract tags from Python code using regex fallback', async () => {
    const testFile = path.join(tempDir, 'test.py');
    const code = `
def greet(name: str) -> str:
    return f"Hello, {name}!"

class Greeter:
    def __init__(self, name: str):
        self.name = name

    def greet(self) -> str:
        return f"Hello, {self.name}!"

async def async_greet(name: str) -> str:
    return f"Hello async, {name}!"
`;

    await fs.writeFile(testFile, code);

    const extractor = new TagExtractor();
    const tags = await extractor.getTags(testFile, 'test.py');

    // 检查是否提取到了一些 tags
    expect(tags.length).toBeGreaterThan(0);

    // 检查是否有定义
    const defs = tags.filter((t) => t.kind === 'def');
    expect(defs.length).toBeGreaterThan(0);

    // 检查是否包含函数和类
    const defNames = defs.map((t) => t.name);
    expect(defNames).toContain('greet');
    expect(defNames).toContain('Greeter');
    expect(defNames).toContain('async_greet');
  });
});

describe('RepoMap', () => {
  let tempDir: string;

  beforeAll(async () => {
    tempDir = path.join(os.tmpdir(), `repomap-test-${Date.now()}`);
    await fs.mkdir(tempDir, { recursive: true });

    // 创建测试文件
    await fs.writeFile(
      path.join(tempDir, 'utils.ts'),
      `
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}
`
    );

    await fs.writeFile(
      path.join(tempDir, 'calculator.ts'),
      `
import { add, multiply } from './utils';

export class Calculator {
  add(a: number, b: number): number {
    return add(a, b);
  }

  multiply(a: number, b: number): number {
    return multiply(a, b);
  }
}
`
    );

    await fs.writeFile(
      path.join(tempDir, 'main.ts'),
      `
import { Calculator } from './calculator';

const calc = new Calculator();
console.log(calc.add(1, 2));
console.log(calc.multiply(3, 4));
`
    );
  });

  it('should create a repo map', async () => {
    const repoMap = createRepoMap(tempDir, {
      mapTokens: 2048,
    });

    const files = [
      path.join(tempDir, 'utils.ts'),
      path.join(tempDir, 'calculator.ts'),
      path.join(tempDir, 'main.ts'),
    ];

    const map = await repoMap.getRepoMap([], files, new Set(), new Set());

    // 应该生成了一些内容
    expect(map.length).toBeGreaterThan(0);
  });

  it('should boost mentioned identifiers', async () => {
    const repoMap = createRepoMap(tempDir, {
      mapTokens: 2048,
    });

    const files = [
      path.join(tempDir, 'utils.ts'),
      path.join(tempDir, 'calculator.ts'),
      path.join(tempDir, 'main.ts'),
    ];

    const mentionedIdents = new Set(['Calculator']);
    const map = await repoMap.getRepoMap([], files, new Set(), mentionedIdents);

    // 应该包含 Calculator 相关内容
    expect(map.length).toBeGreaterThan(0);
  });

  it('should exclude chat files from output', async () => {
    const repoMap = createRepoMap(tempDir, {
      mapTokens: 2048,
    });

    const chatFiles = [path.join(tempDir, 'utils.ts')];
    const otherFiles = [
      path.join(tempDir, 'calculator.ts'),
      path.join(tempDir, 'main.ts'),
    ];

    const map = await repoMap.getRepoMap(chatFiles, otherFiles, new Set(), new Set());

    // utils.ts 不应该出现在输出中
    expect(map).not.toContain('utils.ts:');
  });
});
