import { describe, it, expect } from 'vitest';
import { resourceSuggestions, planningSuggestions, combatSuggestions } from './advisors';
import { buildSuggestedEncounter, suggestReinforcements } from './encounter';
import { characterDefaults, type Character, type Note, type Encounter } from '@/lib/schemas';

function pc(name: string, cur: number, max: number, over: Partial<Character> = {}): Character {
  return {
    id: name, campaignId: 'c', system: '5e', kind: 'pc', name, ancestry: '', className: '', level: 3,
    abilities: { str: 10, dex: 10, con: 10, int: 10, wis: 10, cha: 10 },
    hp: { current: cur, max, temp: 0 }, speed: 30, armorBonus: 0,
    skillRanks: {}, saveRanks: {}, perceptionRank: 'trained',
    ...characterDefaults(), notes: '', createdAt: '', updatedAt: '', ...over,
  };
}

describe('resourceSuggestions', () => {
  it('flags downed and bloodied PCs', () => {
    const s = resourceSuggestions([pc('Down', 0, 20), pc('Hurt', 5, 20), pc('Fine', 20, 20)]);
    expect(s.find((x) => x.id === 'down-Down')?.severity).toBe('danger');
    expect(s.find((x) => x.id === 'bloodied-Hurt')?.action?.type).toBe('longRest');
    expect(s.some((x) => x.id === 'bloodied-Fine')).toBe(false);
  });
  it('suggests rest when the party is low overall', () => {
    const s = resourceSuggestions([pc('A', 4, 20), pc('B', 5, 20)]);
    expect(s.some((x) => x.id === 'party-low')).toBe(true);
  });
});

describe('planningSuggestions', () => {
  const note = (title: string, body = ''): Note => ({ id: title, campaignId: 'c', title, body, tags: [], createdAt: '', updatedAt: '' });
  it('surfaces dangling wiki links as create-note actions', () => {
    const s = planningSuggestions([note('Home', 'Go to [[Castle]]')], []);
    const d = s.find((x) => x.title.includes('Castle'));
    expect(d?.action).toMatchObject({ type: 'createNote', title: 'Castle' });
  });
  it('does not flag links that resolve', () => {
    const s = planningSuggestions([note('Home', 'See [[Castle]]'), note('Castle')], []);
    expect(s.some((x) => x.title.includes('Missing note'))).toBe(false);
  });
});

describe('combatSuggestions', () => {
  const enc = (over: Partial<Encounter> = {}): Encounter => ({
    id: 'e', campaignId: 'c', name: 'E', status: 'active', round: 2, turnIndex: 0,
    combatants: [], log: [], createdAt: '', updatedAt: '', ...over,
  });
  it('returns nothing when no active combat', () => {
    expect(combatSuggestions(undefined)).toEqual([]);
    expect(combatSuggestions(enc({ status: 'planning' }))).toEqual([]);
  });
  it('reports the current turn and downed combatants', () => {
    const e = enc({
      combatants: [
        { id: 'a', name: 'Hero', kind: 'pc', initiative: 10, initBonus: 0, ac: 12, hp: { current: 5, max: 10, temp: 0 }, conditions: [], notes: '' },
        { id: 'b', name: 'Goblin', kind: 'monster', initiative: 8, initBonus: 0, ac: 13, hp: { current: 0, max: 7, temp: 0 }, conditions: [], notes: '' },
      ],
    });
    const s = combatSuggestions(e);
    expect(s.some((x) => x.title.includes("Hero's turn"))).toBe(true);
    expect(s.find((x) => x.id === 'down-combatants')?.detail).toContain('Goblin');
  });
});

describe('buildSuggestedEncounter', () => {
  it('reaches at least the target difficulty for 5e', () => {
    const pool = [{ cr: 0.25 }, { cr: 0.5 }, { cr: 1 }, { cr: 2 }];
    const chosen = buildSuggestedEncounter('5e', [3, 3, 3, 3], pool, 'Medium');
    expect(chosen.length).toBeGreaterThan(0);
    expect(chosen.length).toBeLessThanOrEqual(8);
  });
  it('returns nothing when the pool has no eligible creatures', () => {
    expect(buildSuggestedEncounter('5e', [1, 1, 1, 1], [{ cr: 25 }], 'Hard')).toEqual([]);
  });
});

describe('suggestReinforcements', () => {
  const pool = [{ name: 'Goblin', cr: 0.25 }, { name: 'Hobgoblin', cr: 0.5 }, { name: 'Ogre', cr: 2 }];

  it('adds more of the existing creature to raise difficulty', () => {
    // 3 goblins vs two level-3 PCs is "medium"; bump toward Hard.
    const existing = [{ name: 'Goblin', cr: 0.25 }, { name: 'Goblin 2', cr: 0.25 }, { name: 'Goblin 3', cr: 0.25 }];
    const plan = suggestReinforcements('5e', [3, 3], existing, pool, 'Hard');
    expect(plan).not.toBeNull();
    expect(plan!.add[0]!.name).toBe('Goblin');
    expect(plan!.add[0]!.count).toBeGreaterThanOrEqual(1);
    expect(plan!.reasoning).toMatch(/goblin/i);
  });

  it('uses the existing creature name without the auto-number suffix', () => {
    const plan = suggestReinforcements('5e', [3, 3], [{ name: 'Goblin 2', cr: 0.25 }], pool, 'Hard');
    expect(plan!.add[0]!.name).toBe('Goblin');
  });

  it('returns null for an empty encounter (caller builds fresh instead)', () => {
    expect(suggestReinforcements('5e', [3, 3], [], pool, 'Hard')).toBeNull();
  });
});
