import { describe, it, expect } from 'vitest';
import type { Graph, AspectDef, GraphNode } from '../../../src/model/graph.js';
import { computeAspectUsage, formatAspectsOutput } from '../../../src/cli/aspects.js';

function makeAspect(id: string, overrides: Partial<Omit<AspectDef, 'reviewer'>> & { reviewer?: string | AspectDef['reviewer'] } = {}): AspectDef {
  const { reviewer: rev, ...rest } = overrides;
  const reviewerSpec: AspectDef['reviewer'] =
    rev === undefined ? { type: 'llm' } :
    typeof rev === 'string' ? { type: rev as 'llm' | 'deterministic' } :
    rev;
  return {
    id,
    name: id.charAt(0).toUpperCase() + id.slice(1),
    description: `Description of ${id}`,
    reviewer: reviewerSpec,
    artifacts: [],
    ...rest,
  };
}

function makeNode(
  path: string,
  aspects: string[] = [],
  type: string = 'module',
): GraphNode {
  return {
    path,
    meta: {
      name: path.split('/').pop() || path,
      type,
      aspects,
    },
    children: [],
    parent: null,
  };
}

function makeGraph(aspects: AspectDef[], nodes: GraphNode[] = []): Graph {
  const nodesMap = new Map<string, GraphNode>();
  for (const node of nodes) {
    nodesMap.set(node.path, node);
  }

  return {
    rootPath: '/fake',
    config: { version: '1' },
    architecture: {
      node_types: {
        module: { description: 'Module', aspects: [] },
      },
    },
    nodes: nodesMap,
    aspects,
    flows: [],
    schemas: [],
  } as Graph;
}

describe('computeAspectUsage', () => {
  it('counts zero usage for orphaned aspect', () => {
    const graph = makeGraph([makeAspect('deterministic')]);
    const usage = computeAspectUsage(graph);
    expect(usage.get('deterministic')?.total).toBe(0);
  });

  it('counts own declaration usage', () => {
    const nodes = [makeNode('cli/core', ['deterministic'])];
    const graph = makeGraph([makeAspect('deterministic')], nodes);
    const usage = computeAspectUsage(graph);
    expect(usage.get('deterministic')?.total).toBe(1);
    expect(usage.get('deterministic')?.own).toBe(1);
  });

  it('counts multiple nodes using same aspect', () => {
    const nodes = [
      makeNode('cli/core', ['deterministic']),
      makeNode('cli/commands', ['deterministic']),
    ];
    const graph = makeGraph([makeAspect('deterministic')], nodes);
    const usage = computeAspectUsage(graph);
    expect(usage.get('deterministic')?.total).toBe(2);
    expect(usage.get('deterministic')?.own).toBe(2);
  });

  it('counts multiple aspect sources separately', () => {
    const nodes = [
      makeNode('cli/own', ['own-aspect']),
      makeNode('cli/flow', []),
    ];
    const graph = makeGraph(
      [makeAspect('own-aspect'), makeAspect('flow-aspect')],
      nodes,
    );
    graph.flows = [
      {
        path: 'test-flow',
        name: 'Test Flow',
        nodes: ['cli/flow'],
        aspects: ['flow-aspect'],
      },
    ];
    const usage = computeAspectUsage(graph);
    expect(usage.get('own-aspect')?.own).toBe(1);
    expect(usage.get('flow-aspect')?.flow).toBe(1);
  });

});

describe('formatAspectsOutput', () => {
  it('shows usage stats per aspect', () => {
    const aspects = [makeAspect('deterministic')];
    const nodes = [makeNode('cli/core', ['deterministic'])];
    const graph = makeGraph(aspects, nodes);
    const output = formatAspectsOutput(graph);
    expect(output).toContain('deterministic');
    expect(output).toContain('Used by:');
  });

  it('shows orphaned label for unused aspect', () => {
    const aspects = [makeAspect('unused-aspect')];
    const graph = makeGraph(aspects);
    const output = formatAspectsOutput(graph);
    expect(output).toContain('orphaned');
  });

  it('shows implies when present', () => {
    const aspects = [makeAspect('parent-aspect', { implies: ['child-aspect'] })];
    const graph = makeGraph(aspects);
    const output = formatAspectsOutput(graph);
    expect(output).toContain('Implies: child-aspect');
  });

  it('handles aspect with no description', () => {
    const aspects = [makeAspect('bare', { description: undefined })];
    const graph = makeGraph(aspects);
    const output = formatAspectsOutput(graph);
    expect(output).toContain('bare');
    expect(output).not.toContain('undefined');
  });

  it('shows multiple nodes count', () => {
    const aspects = [makeAspect('shared')];
    const nodes = [
      makeNode('cli/core', ['shared']),
      makeNode('cli/commands', ['shared']),
      makeNode('cli/utils', ['shared']),
    ];
    const graph = makeGraph(aspects, nodes);
    const output = formatAspectsOutput(graph);
    expect(output).toContain('Used by: 3 nodes');
  });

  it('shows single node count correctly', () => {
    const aspects = [makeAspect('single')];
    const nodes = [makeNode('cli/core', ['single'])];
    const graph = makeGraph(aspects, nodes);
    const output = formatAspectsOutput(graph);
    expect(output).toContain('Used by: 1 node');
  });

  it('shows Reviewer: deterministic for deterministic aspects', () => {
    const aspects = [makeAspect('async-fs', { reviewer: 'deterministic' })];
    const graph = makeGraph(aspects);
    const output = formatAspectsOutput(graph);
    expect(output).toMatch(/Reviewer:\s*deterministic/);
  });

  it('shows Reviewer: llm for aspects without explicit reviewer', () => {
    const aspects = [makeAspect('posix-paths')];
    const graph = makeGraph(aspects);
    const output = formatAspectsOutput(graph);
    expect(output).toMatch(/Reviewer:\s*llm/);
  });

  it('shows Reviewer: llm for aspects with explicit reviewer: llm', () => {
    const aspects = [makeAspect('explicit-llm', { reviewer: 'llm' })];
    const graph = makeGraph(aspects);
    const output = formatAspectsOutput(graph);
    expect(output).toMatch(/Reviewer:\s*llm/);
  });

  it('shows [enforced] tag when aspect has no explicit status', () => {
    const aspects = [makeAspect('default-status')];
    const graph = makeGraph(aspects);
    const output = formatAspectsOutput(graph);
    expect(output).toContain('default-status [enforced]');
  });

  it('shows declared status tag from aspect definition', () => {
    const aspects = [
      makeAspect('draft-aspect', { status: 'draft' }),
      makeAspect('advisory-aspect', { status: 'advisory' }),
    ];
    const graph = makeGraph(aspects);
    const output = formatAspectsOutput(graph);
    expect(output).toContain('advisory-aspect [advisory]');
    expect(output).toContain('draft-aspect [draft]');
  });
});
