import { describe, expect, it } from 'vitest';

import { healthCatalog, resolveHealthChoice, HEALTH_FLAGS } from '../../src/events/v2/catalog/health.js';
import { EventResponder } from '../../src/events/v2/engine/respond.js';
import { isEventEligible, playerHasFlag } from '../../src/events/v2/engine/selector.js';
import { createEventRegistry } from '../../src/events/v2/registry.js';
import { applyEventEffects } from '../../src/events/v2/engine/effects.js';
import type { EventDefinition, EventPlayerContext } from '../../src/events/v2/types.js';

function makePlayer(overrides: Partial<EventPlayerContext> = {}): EventPlayerContext {
  return {
    userId: 'p1',
    c: { ageYears: 30, money: 8000, happiness: 50, stress: 60, energy: 100, health: 80, social: 50 },
    askedQuestions: new Set<string>(),
    eventLastFired: {},
    flags: new Set<string>(),
    ...overrides,
  };
}

function findEvent(id: string): EventDefinition {
  const def = healthCatalog.find((event) => event.id === id);
  expect(def, `health event ${id} should exist`).toBeDefined();
  return def!;
}

function makeStore(eventId: string) {
  let pending = [{ instanceId: 'inst-1', playerId: 'p1', eventId, status: 'pending' as const }];
  return {
    getPendingEventInstances: async () => pending,
    answerEventInstance: async () => true,
    resolveEventInstance: async () => {
      pending = [];
      return true;
    },
  };
}

describe('events v2 health catalog: structure', () => {
  it('registers the medical arcs with unique, namespaced ids and choices', () => {
    expect(healthCatalog.length).toBeGreaterThanOrEqual(12);

    const ids = healthCatalog.map((e) => e.id);
    expect(new Set(ids).size).toBe(ids.length);

    for (const def of healthCatalog) {
      expect(def.category).toBe('health');
      expect(def.id.startsWith('health.')).toBe(true);
      expect(def.choices.length).toBeGreaterThan(0);
      const choiceIds = def.choices.map((c) => c.choiceId);
      expect(new Set(choiceIds).size).toBe(choiceIds.length);
      for (const c of def.choices) {
        expect(resolveHealthChoice(def.id, c.choiceId)).toBe(c.resolutionText);
      }
    }
  });

  it('returns null for unknown event/choice in the resolver', () => {
    expect(resolveHealthChoice('health.nope', 'x')).toBeNull();
    expect(resolveHealthChoice('health.diagnosisReveal', 'nope')).toBeNull();
  });
});

describe('events v2 health catalog: eligibility', () => {
  it('makes the stage-1 arcs eligible for a stressed 30-year-old', () => {
    const player = makePlayer();
    for (const id of ['health.diagnosisReveal', 'health.medicalEmergency', 'health.therapyStart']) {
      expect(isEventEligible(findEvent(id), player, 1), `${id} eligible`).toBe(true);
    }
  });

  it('gates therapyStart on stress > 50 or happiness < 35', () => {
    const calm = makePlayer({ c: { ageYears: 30, stress: 10, happiness: 80 } });
    expect(isEventEligible(findEvent('health.therapyStart'), calm, 1)).toBe(false);
    const sad = makePlayer({ c: { ageYears: 30, stress: 10, happiness: 30 } });
    expect(isEventEligible(findEvent('health.therapyStart'), sad, 1)).toBe(true);
  });

  it('gates pregnancyDiscovery on a living spouse/partner', () => {
    const single = makePlayer({ c: { ageYears: 30, stress: 10, happiness: 80 } });
    expect(isEventEligible(findEvent('health.pregnancyDiscovery'), single, 1)).toBe(false);
    const partnered = makePlayer({
      c: { ageYears: 30 },
      r: [{ status: 'alive', relationships: ['spouse'] }],
    } as Partial<EventPlayerContext>);
    expect(isEventEligible(findEvent('health.pregnancyDiscovery'), partnered, 1)).toBe(true);
  });

  it('respects medicalEmergency minAge of 5', () => {
    const baby = makePlayer({ c: { ageYears: 3 } });
    expect(isEventEligible(findEvent('health.medicalEmergency'), baby, 1)).toBe(false);
  });

  it('keeps follow-up stages ineligible until their gate fires', () => {
    const player = makePlayer();
    expect(isEventEligible(findEvent('health.diagnosisTreatment'), player, 1)).toBe(false);
    expect(isEventEligible(findEvent('health.emergencyRecovery'), player, 1)).toBe(false);
    expect(isEventEligible(findEvent('health.therapyProgress'), player, 1)).toBe(false);
  });
});

describe('events v2 health catalog: flag-gated multi-stage arcs', () => {
  it('a tested diagnosis choice sets the diagnosed flag and unlocks treatment, not the worsening branch', async () => {
    const player = makePlayer();
    const registry = createEventRegistry(healthCatalog);
    const responder = new EventResponder(registry, makeStore('health.diagnosisReveal'));

    const result = await responder.respond(player, { eventId: 'health.diagnosisReveal', choiceId: 'tests' });
    expect(result.type).toBe('event_resolved');

    expect(playerHasFlag(player, HEALTH_FLAGS.diagnosed)).toBe(true);
    expect(isEventEligible(findEvent('health.diagnosisTreatment'), player, 1)).toBe(true);
    expect(isEventEligible(findEvent('health.diagnosisSkippedWorsens'), player, 1)).toBe(false);
  });

  it('skipping diagnosis sets no flag and unlocks the worsening branch instead', async () => {
    const player = makePlayer();
    const registry = createEventRegistry(healthCatalog);
    const responder = new EventResponder(registry, makeStore('health.diagnosisReveal'));

    await responder.respond(player, { eventId: 'health.diagnosisReveal', choiceId: 'skip' });

    expect(playerHasFlag(player, HEALTH_FLAGS.diagnosed)).toBe(false);
    expect(isEventEligible(findEvent('health.diagnosisTreatment'), player, 1)).toBe(false);
    expect(isEventEligible(findEvent('health.diagnosisSkippedWorsens'), player, 1)).toBe(true);
  });

  it('starting therapy unlocks progress, and progress unlocks completion', async () => {
    const player = makePlayer();
    const registry = createEventRegistry(healthCatalog);
    const responder = new EventResponder(registry, makeStore('health.therapyStart'));

    await responder.respond(player, { eventId: 'health.therapyStart', choiceId: 'start' });
    expect(playerHasFlag(player, HEALTH_FLAGS.inTherapy)).toBe(true);
    expect(isEventEligible(findEvent('health.therapyProgress'), player, 1)).toBe(true);
    // Completion not yet eligible until progress fires.
    expect(isEventEligible(findEvent('health.therapyCompletion'), player, 1)).toBe(false);

    (player.askedQuestions as Set<string>).add('health.therapyProgress');
    expect(isEventEligible(findEvent('health.therapyCompletion'), player, 1)).toBe(true);
  });

  it('declining therapy (think) sets no flag, so no follow-up unlocks', async () => {
    const player = makePlayer();
    const registry = createEventRegistry(healthCatalog);
    const responder = new EventResponder(registry, makeStore('health.therapyStart'));

    await responder.respond(player, { eventId: 'health.therapyStart', choiceId: 'think' });
    expect(playerHasFlag(player, HEALTH_FLAGS.inTherapy)).toBe(false);
    expect(isEventEligible(findEvent('health.therapyProgress'), player, 1)).toBe(false);
  });

  it('pregnancy discovery (joy) chains all five stages via flag + wasAsked', async () => {
    const player = makePlayer({
      c: { ageYears: 30 },
      r: [{ status: 'alive', relationships: ['partner'] }],
    } as Partial<EventPlayerContext>);
    const registry = createEventRegistry(healthCatalog);
    const responder = new EventResponder(registry, makeStore('health.pregnancyDiscovery'));

    await responder.respond(player, { eventId: 'health.pregnancyDiscovery', choiceId: 'joy' });
    expect(playerHasFlag(player, HEALTH_FLAGS.pregnant)).toBe(true);

    const asked = player.askedQuestions as Set<string>;
    expect(isEventEligible(findEvent('health.pregnancyTrimester1'), player, 1)).toBe(true);
    asked.add('health.pregnancyTrimester1');
    expect(isEventEligible(findEvent('health.pregnancyMidpoint'), player, 1)).toBe(true);
    asked.add('health.pregnancyMidpoint');
    expect(isEventEligible(findEvent('health.pregnancyTrimester3'), player, 1)).toBe(true);
    asked.add('health.pregnancyTrimester3');
    expect(isEventEligible(findEvent('health.pregnancyBirth'), player, 1)).toBe(true);
  });

  it('the "discuss" pregnancy branch sets no flag, so no later stage fires', async () => {
    const player = makePlayer({
      c: { ageYears: 30 },
      r: [{ status: 'alive', relationships: ['partner'] }],
    } as Partial<EventPlayerContext>);
    const registry = createEventRegistry(healthCatalog);
    const responder = new EventResponder(registry, makeStore('health.pregnancyDiscovery'));

    await responder.respond(player, { eventId: 'health.pregnancyDiscovery', choiceId: 'discuss' });
    expect(playerHasFlag(player, HEALTH_FLAGS.pregnant)).toBe(false);
    expect(isEventEligible(findEvent('health.pregnancyTrimester1'), player, 1)).toBe(false);
  });
});

describe('events v2 health catalog: effects apply', () => {
  it('applies the medical emergency 911 cost + stat hits', () => {
    const player = makePlayer({ c: { ageYears: 40, money: 10000, health: 80, energy: 100, stress: 20 } });
    const choice = findEvent('health.medicalEmergency').choices.find((c) => c.choiceId === 'emergency')!;
    applyEventEffects(player as { c: Record<string, unknown> }, {
      ...choice.effects,
      resources: { money: -(choice.moneyCost ?? 0), ...choice.effects?.resources },
    });
    expect(player.c.money).toBe(5000);
    expect(player.c.health).toBe(70);
    expect(player.c.energy).toBe(75);
  });

  it('applies the therapy completion happiness/stress deltas', () => {
    const player = makePlayer({ c: { ageYears: 30, happiness: 50, stress: 40 } });
    const choice = findEvent('health.therapyCompletion').choices[0];
    applyEventEffects(player as { c: Record<string, unknown> }, choice.effects);
    expect(player.c.happiness).toBe(62);
    expect(player.c.stress).toBe(30);
  });
});
