import { describe, it, expect, vi } from 'vitest';
import { createCouncilRunTool } from './council-run.js';
import type { AgentConfigRegistry } from '../../agents/registry.js';
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

function createRegistry(): AgentConfigRegistry {
  const configs = new Map<string, { name: string; modelTier?: 'fast' | 'default' | 'complex'; systemPrompt?: string }>([
    ['council_d_arbiter', { name: 'council_d_arbiter', modelTier: 'default', systemPrompt: 'D Arbiter' }],
    ['council_d_freethinker', { name: 'council_d_freethinker', modelTier: 'default', systemPrompt: 'D FT' }],
    ['council_p_arbiter', { name: 'council_p_arbiter', modelTier: 'default', systemPrompt: 'P Arbiter' }],
    ['council_p_freethinker', { name: 'council_p_freethinker', modelTier: 'default', systemPrompt: 'P FT' }],
    ['council_meta_arbiter', { name: 'council_meta_arbiter', modelTier: 'default', systemPrompt: 'Meta' }],
  ]);
  return {
    get: (name: string) => configs.get(name),
    list: () => [...configs.values()],
  } as unknown as AgentConfigRegistry;
}

const config = {
  enabled: true,
  defaults: {
    max_rounds: 1,
    ideas_per_round: 2,
    top_ideas_for_bridge: 1,
    bridge_packet_max_chars: 5000,
    bridge_field_max_bullets: 5,
    bridge_entry_max_chars: 300,
    novelty_delta_threshold: 10,
    repetition_threshold: 70,
  },
  strict_grounding: false,
  strict_meta_validation: true,
  groups: {
    D: {
      arbiter_agent: 'council_d_arbiter',
      freethinker_agent: 'council_d_freethinker',
      group_prompt_prefix: 'D',
      novelty_bias: 'low',
      risk_tolerance: 'low',
      forbidden_approaches: [],
    },
    P: {
      arbiter_agent: 'council_p_arbiter',
      freethinker_agent: 'council_p_freethinker',
      group_prompt_prefix: 'P',
      novelty_bias: 'high',
      risk_tolerance: 'high',
      forbidden_approaches: [],
    },
  },
  meta_arbiter_agent: 'council_meta_arbiter',
} as const;

describe('council.run tool', () => {
  it('sets extended tool timeout for council orchestration', () => {
    const tool = createCouncilRunTool({
      registry: createRegistry(),
      orchestrator: { delegate: vi.fn() as any },
      config: config as any,
    });
    expect(tool.timeoutMs).toBe(180000);
  });

  it('runs council pipeline and returns output summary', async () => {
    const previousDataDir = process.env.FLYNN_DATA_DIR;
    const testDataDir = mkdtempSync(join(tmpdir(), 'flynn-council-run-'));
    process.env.FLYNN_DATA_DIR = testDataDir;

    const delegate = vi.fn(async ({ message }: { message: string }) => {
      const payload = JSON.parse(message);
      if (payload.brief_D && payload.brief_P) {
        return {
          content: JSON.stringify({
            schema_version: '1.0.0',
            selected_primary: [payload.brief_D.shortlist[0]],
            selected_secondary: [payload.brief_P.shortlist[0]],
            merges: [],
            rejections: [],
            open_questions: ['q1'],
            next_experiments: ['e1'],
          }),
          usage: { inputTokens: 10, outputTokens: 5 },
          tier: 'default' as const,
        };
      }
      if (payload.shortlisted_ideas) {
        return {
          content: JSON.stringify({ grounded: payload.shortlisted_ideas.map((idea: any) => ({ idea_id: idea.idea_id, mve: 'm', constraints: ['c'], falsifiability_checks: ['f'] })) }),
          usage: { inputTokens: 10, outputTokens: 5 },
          tier: 'default' as const,
        };
      }
      if (payload.ideas) {
        return {
          content: JSON.stringify({
            assessments: payload.ideas.map((idea: any, idx: number) => ({
              idea_id: idea.idea_id,
              scores: { novelty: 50, feasibility: 50, impact: 50, testability: 50 },
              decision: idx === 0 ? 'shortlist' : 'hold',
              notes: 'note',
            })),
            assumptions: ['a'],
            risks: ['r'],
            asks: ['k'],
            what_to_steal: ['w'],
            convergence_signal: false,
            novelty_score: 60,
            repetition_rate: 10,
          }),
          usage: { inputTokens: 10, outputTokens: 5 },
          tier: 'default' as const,
        };
      }
      return {
        content: JSON.stringify({
          ideas: [
            { title: 't1', hypothesis: 'h1', mechanism: 'm1', expected_outcome: 'o1' },
            { title: 't2', hypothesis: 'h2', mechanism: 'm2', expected_outcome: 'o2' },
          ],
        }),
        usage: { inputTokens: 10, outputTokens: 5 },
        tier: 'default' as const,
      };
    });

    const tool = createCouncilRunTool({
      registry: createRegistry(),
      orchestrator: { delegate },
      config: config as any,
    });

    const result = await tool.execute({ task: 'plan migration' });
    try {
      expect(result.success).toBe(true);
      expect(result.output).toContain('Council pipeline v1.0.0');
      expect(result.output).toContain('Meta selection');
      expect(result.output).toContain('Timing:');
      expect(result.output).toContain('Slowest calls:');
      expect(result.output).toContain('Artifacts:');

      const markdownLine = result.output.split('\n').find((line) => line.startsWith('- Summary report: '));
      const jsonLine = result.output.split('\n').find((line) => line.startsWith('- Full JSON: '));
      expect(markdownLine).toBeDefined();
      expect(jsonLine).toBeDefined();

      const markdownPath = markdownLine!.replace('- Summary report: ', '').trim();
      const jsonPath = jsonLine!.replace('- Full JSON: ', '').trim();
      expect(readFileSync(markdownPath, 'utf-8')).toContain('Council pipeline v1.0.0');
      expect(readFileSync(jsonPath, 'utf-8')).toContain('"pipeline_version": "1.0.0"');
    } finally {
      if (previousDataDir !== undefined) {
        process.env.FLYNN_DATA_DIR = previousDataDir;
      } else {
        delete process.env.FLYNN_DATA_DIR;
      }
      rmSync(testDataDir, { recursive: true, force: true });
    }
  });

  it('returns error on invalid input', async () => {
    const tool = createCouncilRunTool({
      registry: createRegistry(),
      orchestrator: { delegate: vi.fn() as any },
      config: config as any,
    });
    const result = await tool.execute({});
    expect(result.success).toBe(false);
    expect(result.error).toBeDefined();
  });

  it('classifies network/latency failures with hint and council config summary', async () => {
    const tool = createCouncilRunTool({
      registry: createRegistry(),
      orchestrator: {
        delegate: vi.fn(async () => {
          throw new Error('Connection error.');
        }) as any,
      },
      config: config as any,
    });

    const result = await tool.execute({ task: 'x' });
    expect(result.success).toBe(false);
    expect(result.error).toContain('Likely root cause: network_or_latency');
    expect(result.error).toContain('Hint: Likely network/provider latency issue.');
    expect(result.error).toContain('Council config: D=');
  });

  it('classifies cap_exceeded failures with cap_overflow hint', async () => {
    const tool = createCouncilRunTool({
      registry: createRegistry(),
      orchestrator: {
        delegate: vi.fn(async () => {
          throw new Error('cap_exceeded');
        }) as any,
      },
      config: config as any,
    });

    const result = await tool.execute({ task: 'x' });
    expect(result.success).toBe(false);
    expect(result.error).toContain('Likely root cause: cap_overflow');
    expect(result.error).toContain('Bridge payload cap exceeded.');
  });

  it('classifies cancellation failures with user_cancelled hint', async () => {
    const tool = createCouncilRunTool({
      registry: createRegistry(),
      orchestrator: {
        delegate: vi.fn(async () => {
          throw new Error('Operation cancelled by user.');
        }) as any,
      },
      config: config as any,
    });

    const result = await tool.execute({ task: 'x' });
    expect(result.success).toBe(false);
    expect(result.error).toContain('Likely root cause: user_cancelled');
    expect(result.error).toContain('Run was cancelled by user input');
  });
});
