/**
 * Tests for the generational / legacy system (T010b).
 *
 * Death is the BEGINNING of the next session: on death the eldest living child
 * is selected as heir, a fraction of net worth is passed down as inheritance, a
 * compounding family prestige is accrued, and a persistent family tree gains an
 * entry. The continue-as-heir branch of startNewLife preserves the generational
 * layer and seeds the heir; a fresh start wipes it.
 */
import { describe, it, expect, beforeEach } from 'vitest';
import { Person } from '../../../src/models/Person.js';
import { Player } from '../../../src/models/Player.js';
import { config } from '../../../src/config.js';
import {
  handleDeath,
  buildLegacy,
  buildLifeSummary,
  computeInheritance,
  computePrestigeGain,
  ESTATE_TAX_RETENTION,
  HEIR_AFFINITY_BONUS_MAX,
  type HeirInfo,
} from '../../../src/services/health/health_manager.js';
import { getEldestLivingChild } from '../../../src/services/character/character_manager.js';
import { handleStartNewLife } from '../../../src/handlers/gameControl.js';
import { clearAllStatistics } from '../../../src/services/retention/statistics.js';

class MockPlayerSession {
  player: Player;
  sentMessages: any[] = [];
  stopped = false;

  constructor(player: Player) {
    this.player = player;
  }

  send(message: any) {
    this.sentMessages.push(message);
  }

  sendPlayerObject() {
    this.sentMessages.push({ type: 'playerObject', player: this.player });
  }

  stop() {
    this.stopped = true;
  }

  async savePlayer() {
    return undefined;
  }
}

let childCounter = 0;

function makeChild(opts: {
  ageYears: number;
  affinity?: number;
  status?: 'alive' | 'dead';
  firstname?: string;
}): Person {
  childCounter += 1;
  return new Person({
    id: `child-${childCounter}`,
    firstname: opts.firstname ?? `Kid${childCounter}`,
    lastname: 'Heir',
    sex: childCounter % 2 === 0 ? 'Female' : 'Male',
    status: opts.status ?? 'alive',
    relationships: ['child', 'family'],
    ageYears: opts.ageYears,
    ageDays: opts.ageYears * 365,
    affinity: opts.affinity ?? 50,
  } as never);
}

function makePlayer(opts: {
  money?: number;
  ageYears?: number;
  children?: Person[];
  familyPrestige?: number;
} = {}): Player {
  const character = new Person({
    id: 'char-1',
    firstname: 'Patriarch',
    lastname: 'Heir',
    sex: 'Male',
    status: 'alive',
    ageYears: opts.ageYears ?? 80,
    money: opts.money ?? 10000,
  } as never);
  const player = new Player({
    userId: 'user-legacy',
    character,
    status: 'playing',
    controller: 'active',
    r: opts.children ?? [],
  });
  if (opts.familyPrestige !== undefined) {
    player.familyPrestige = opts.familyPrestige;
  }
  return player;
}

describe('legacy: heir selection', () => {
  beforeEach(() => clearAllStatistics());

  it('picks the ELDEST living child as heir', () => {
    const young = makeChild({ ageYears: 5 });
    const eldest = makeChild({ ageYears: 25, firstname: 'Firstborn' });
    const middle = makeChild({ ageYears: 15 });
    const player = makePlayer({ children: [young, eldest, middle] });

    const heir = getEldestLivingChild(player);
    expect(heir?.id).toBe(eldest.id);
    expect(heir?.firstname).toBe('Firstborn');
  });

  it('skips dead children when selecting the heir', () => {
    const deadEldest = makeChild({ ageYears: 30, status: 'dead' });
    const livingNext = makeChild({ ageYears: 20, firstname: 'Survivor' });
    const player = makePlayer({ children: [deadEldest, livingNext] });

    const heir = getEldestLivingChild(player);
    expect(heir?.firstname).toBe('Survivor');
  });

  it('returns null (line ends) when there is no living child', () => {
    const onlyDead = makeChild({ ageYears: 30, status: 'dead' });
    const player = makePlayer({ children: [onlyDead] });
    expect(getEldestLivingChild(player)).toBeNull();

    const noKids = makePlayer({ children: [] });
    expect(getEldestLivingChild(noKids)).toBeNull();
  });
});

describe('legacy: inheritance math', () => {
  it('transfers 50% of positive net worth after estate tax (low affinity)', () => {
    const heir: HeirInfo = { id: 'h', name: 'A B', sex: 'Male', ageYears: 20, affinity: 0 };
    // 10000 * 0.5 = 5000, no affinity bonus.
    expect(computeInheritance(10000, heir)).toBe(10000 * ESTATE_TAX_RETENTION);
  });

  it('biases inheritance toward a high-affinity heir', () => {
    const loving: HeirInfo = { id: 'h', name: 'A B', sex: 'Male', ageYears: 20, affinity: 100 };
    // 10000 * 0.5 * (1 + 0.2) = 6000.
    const expected = Math.round(10000 * ESTATE_TAX_RETENTION * (1 + HEIR_AFFINITY_BONUS_MAX));
    expect(computeInheritance(10000, loving)).toBe(expected);
  });

  it('yields no inheritance with no heir or a negative estate', () => {
    expect(computeInheritance(10000, null)).toBe(0);
    const heir: HeirInfo = { id: 'h', name: 'A B', sex: 'Male', ageYears: 20, affinity: 100 };
    expect(computeInheritance(-5000, heir)).toBe(0);
  });

  it('applies the computed inheritance into the lifeSummary legacy on death', () => {
    const eldest = makeChild({ ageYears: 25, affinity: 100, firstname: 'Anointed' });
    const player = makePlayer({ money: 20000, children: [eldest] });

    handleDeath(player);

    const legacy = player.lifeSummary!.legacy;
    expect(legacy.heir?.name).toContain('Anointed');
    const expected = Math.round(20000 * ESTATE_TAX_RETENTION * (1 + HEIR_AFFINITY_BONUS_MAX));
    expect(legacy.inheritance).toBe(expected);
    expect(player.pendingInheritance).toBe(expected);
  });
});

describe('legacy: family prestige compounds', () => {
  beforeEach(() => clearAllStatistics());

  it('computePrestigeGain floors a life score into prestige points', () => {
    expect(computePrestigeGain(0)).toBe(0);
    expect(computePrestigeGain(99)).toBe(0);
    expect(computePrestigeGain(250)).toBe(2);
    expect(computePrestigeGain(-1000)).toBe(0);
  });

  it('compounds family prestige across a death -> continue-as-heir cycle and seeds the heir', async () => {
    const eldest = makeChild({ ageYears: 25, affinity: 80, firstname: 'NextGen' });
    const player = makePlayer({ money: 50000, ageYears: 90, children: [eldest], familyPrestige: 3 });

    // First death: prestige grows by this life's contribution.
    handleDeath(player);
    const gained = player.lifeSummary!.legacy.prestigeGained;
    expect(gained).toBeGreaterThan(0);
    const compounded = 3 + gained;
    expect(player.familyPrestige).toBe(compounded);

    // Continue as heir: prestige is preserved (not wiped) and seeds the new
    // character's starting money + prestige stat.
    const session = new MockPlayerSession(player);
    await handleStartNewLife({ mode: 'heir' }, session as never);

    expect(player.familyPrestige).toBe(compounded);
    expect(player.c.prestige).toBe(compounded);
    // Starting money = inheritance + prestige money seed (familyPrestige * 100).
    expect(player.c.money).toBeGreaterThanOrEqual(compounded * 100);
    // Heir identity carried into the new life.
    expect(player.c.firstname).toBe('NextGen');
  });
});

describe('legacy: persistent family tree', () => {
  beforeEach(() => clearAllStatistics());

  it('appends one entry per death with name/lifespan/career/score', () => {
    const eldest = makeChild({ ageYears: 25 });
    const player = makePlayer({ money: 8000, ageYears: 77, children: [eldest] });
    player.c.job = { title: 'Engineer', levels: [{ salary: 120000 }] } as never;

    handleDeath(player);

    expect(player.familyTree).toHaveLength(1);
    const entry = player.familyTree[0];
    expect(entry.name).toBe('Patriarch Heir');
    expect(entry.finalAge).toBe(77);
    expect(entry.peakCareer).toBe('Engineer');
    expect(entry.score).toBeGreaterThan(0);
    expect(entry.generation).toBe(1);
  });

  it('survives a continue-as-heir new life and grows across generations', async () => {
    const eldest = makeChild({ ageYears: 25, firstname: 'Gen2' });
    const player = makePlayer({ money: 8000, ageYears: 70, children: [eldest] });

    handleDeath(player);
    expect(player.familyTree).toHaveLength(1);

    const session = new MockPlayerSession(player);
    await handleStartNewLife({ mode: 'heir' }, session as never);

    // Tree preserved through the wipe.
    expect(player.familyTree).toHaveLength(1);

    // Live the heir's life, give them a child, and die again.
    const grandchild = makeChild({ ageYears: 18, firstname: 'Gen3' });
    player.c.status = 'alive';
    player.c.money = 12000;
    player.r = [grandchild];
    handleDeath(player);

    expect(player.familyTree).toHaveLength(2);
    expect(player.familyTree[1].generation).toBe(2);
  });
});

describe('legacy: continue-as-heir vs fresh-start', () => {
  beforeEach(() => clearAllStatistics());

  it('continue-as-heir preserves the generational data', async () => {
    const eldest = makeChild({ ageYears: 25, affinity: 70, firstname: 'Scion' });
    const player = makePlayer({ money: 30000, ageYears: 88, children: [eldest], familyPrestige: 2 });

    handleDeath(player);
    const treeLenBefore = player.familyTree.length;
    const prestigeBefore = player.familyPrestige;

    const session = new MockPlayerSession(player);
    await handleStartNewLife({ mode: 'heir' }, session as never);

    expect(player.familyTree.length).toBe(treeLenBefore);
    expect(player.familyPrestige).toBe(prestigeBefore);
    expect(player.c.money).toBeGreaterThan(0);
    expect(player.c.firstname).toBe('Scion');
    expect(player.pendingInheritance).toBe(0);
    expect(player.lifeSummary).toBeNull();
    expect(player.status).toBe('creating');
  });

  it('fresh-start wipes the generational data', async () => {
    const eldest = makeChild({ ageYears: 25, firstname: 'Ignored' });
    const player = makePlayer({ money: 30000, ageYears: 88, children: [eldest], familyPrestige: 5 });

    handleDeath(player);
    expect(player.familyTree.length).toBeGreaterThan(0);

    const session = new MockPlayerSession(player);
    await handleStartNewLife({ mode: 'fresh' }, session as never);

    expect(player.familyPrestige).toBe(0);
    expect(player.familyTree).toEqual([]);
    expect(player.pendingInheritance).toBe(0);
    expect(player.c.money).toBe(0);
    expect(player.c.firstname).toBe('');
  });

  it('type-only / default new life is a fresh start (no heir carryover)', async () => {
    const eldest = makeChild({ ageYears: 25 });
    const player = makePlayer({ money: 30000, children: [eldest], familyPrestige: 4 });

    handleDeath(player);

    const session = new MockPlayerSession(player);
    // No payload (type-only envelope) -> fresh.
    await handleStartNewLife(undefined, session as never);

    expect(player.familyPrestige).toBe(0);
    expect(player.familyTree).toEqual([]);
    expect(player.c.money).toBe(0);
  });

  it("heir mode with no living child falls back to a fresh start", async () => {
    const player = makePlayer({ money: 30000, children: [], familyPrestige: 6 });

    handleDeath(player);
    expect(player.lifeSummary!.legacy.heir).toBeNull();

    const session = new MockPlayerSession(player);
    await handleStartNewLife({ mode: 'heir' }, session as never);

    // No heir -> generational layer wiped like a fresh start.
    expect(player.familyPrestige).toBe(0);
    expect(player.familyTree).toEqual([]);
    expect(player.c.money).toBe(0);
    expect(player.c.firstname).toBe('');
  });
});

describe('legacy: persistence through save/load', () => {
  it('round-trips familyPrestige / familyTree / pendingInheritance via toJSON + constructor', () => {
    const eldest = makeChild({ ageYears: 25 });
    const player = makePlayer({ money: 9000, children: [eldest], familyPrestige: 7 });
    handleDeath(player);

    const json = player.toJSON();
    expect(json.familyPrestige).toBe(player.familyPrestige);
    expect(json.familyTree).toEqual(player.familyTree);
    expect(json.pendingInheritance).toBe(player.pendingInheritance);

    const restored = new Player(json as never);
    expect(restored.familyPrestige).toBe(player.familyPrestige);
    expect(restored.familyTree).toEqual(player.familyTree);
    expect(restored.pendingInheritance).toBe(player.pendingInheritance);
  });
});

describe('legacy: buildLegacy / buildLifeSummary embedding', () => {
  it('buildLifeSummary embeds a legacy payload', () => {
    const eldest = makeChild({ ageYears: 25 });
    const player = makePlayer({ money: 4000, children: [eldest] });
    const summary = buildLifeSummary(player);
    expect(summary.legacy).toBeTruthy();
    expect(summary.legacy.familyTree).toHaveLength(1);
  });

  it('buildLegacy is pure relative to the score it is given', () => {
    const eldest = makeChild({ ageYears: 25, affinity: 50 });
    const player = makePlayer({ money: 2000, children: [eldest] });
    const partial = {
      finalAge: 80,
      netWorth: 2000,
      lifetimeEarnings: 0,
      peakCareer: null,
      relationshipsCount: 1,
      childrenCount: 1,
      achievementsEarned: 0,
      notableEvents: [],
      score: 1200,
      diedAt: new Date().toISOString(),
    };
    const legacy = buildLegacy(player, partial);
    expect(legacy.prestigeGained).toBe(computePrestigeGain(1200));
    expect(legacy.inheritance).toBe(computeInheritance(2000, legacy.heir));
  });
});
