import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { WebSocket } from 'ws';

const {
  savePlayerDbMock,
  saveConversationDbMock,
  checkEventsMock,
  checkTutorialEventsMock,
  checkDilemmasMock,
  updateAgeMock,
  updateDeathChanceMock,
  checkDeathMock,
  getIntradayActivityMock,
  promptNextMock,
} = vi.hoisted(() => ({
  savePlayerDbMock: vi.fn(async () => undefined),
  saveConversationDbMock: vi.fn(async () => undefined),
  checkEventsMock: vi.fn(() => null),
  checkTutorialEventsMock: vi.fn(() => null),
  checkDilemmasMock: vi.fn(() => null),
  updateAgeMock: vi.fn(() => null),
  updateDeathChanceMock: vi.fn(() => 0),
  checkDeathMock: vi.fn(() => false),
  getIntradayActivityMock: vi.fn((_player, person) => person),
  promptNextMock: vi.fn(async () => null),
}));

vi.mock('../../src/database/players.js', () => ({
  savePlayer: savePlayerDbMock,
  saveConversation: saveConversationDbMock,
}));

vi.mock('../../src/stats/stats_manager.js', () => ({
  checkEvents: checkEventsMock,
  checkTutorialEvents: checkTutorialEventsMock,
  checkDilemmas: checkDilemmasMock,
  updateAge: updateAgeMock,
  getPeakEnergy: vi.fn((person: any) => {
    // Real implementation sets peakEnergy and computes calcEnergy
    person.peakEnergy = person.peakEnergy ?? 0;
    person.calcEnergy = (person.energy ?? 100) - person.peakEnergy;
  }),
}));

vi.mock('../../src/services/health/health_manager.js', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../../src/services/health/health_manager.js')>();
  return {
    // Spread the real module so handleDeath (and the life-summary helpers it
    // uses) stay available — PlayerSession now routes the online death path
    // through the shared handleDeath, so the mock must keep it.
    ...actual,
    updateDeathChance: updateDeathChanceMock,
    checkDeath: checkDeathMock,
  };
});

vi.mock('../../src/game/engine/intradayActivity.js', () => ({
  getIntradayActivity: getIntradayActivityMock,
  getDailyPlan: vi.fn(() => []),
}));

vi.mock('../../src/events/v2/runtime.js', () => ({
  getEventRuntime: vi.fn(() => ({
    promptNext: promptNextMock,
  })),
}));

vi.mock('../../src/utils/statUtils.js', () => ({
  clampPlayerStats: vi.fn(),
  cleanStalePendingEvents: vi.fn(),
}));

vi.mock('../../src/utils/messageSanitizer.js', () => ({
  sanitizeOutgoingMessage: vi.fn((msg: unknown) => msg),
}));

import { Person } from '../../src/models/Person.js';
import { Player } from '../../src/models/Player.js';
import { PlayerSession } from '../../src/game/PlayerSession.js';

function createPlayer(): Player {
  const character = new Person({
    id: 'char-1',
    firstname: 'Test',
    lastname: 'Player',
    sex: 'Male',
    ageYears: 20,
    ageDays: 7300,
    ageHours: 175200,
    energy: 80,
    hunger: 10,
    thirst: 10,
    peakEnergy: 10,
    calcEnergy: 70,
    status: 'alive',
  });

  return new Player({
    userId: 'player-1',
    character,
    status: 'playing',
    controller: 'active',
    connection: 'connected',
    hourOfDay: 12,
    minuteOfHour: 0,
    dayOfYear: 100,
    dayOfWeek: 3,
    date: '04-10',
  });
}

function createSession(player: Player): PlayerSession {
  const ws = {
    readyState: 1,
    send: vi.fn(),
  } as unknown as WebSocket;

  return new PlayerSession(ws, player);
}

describe('PlayerSession regression coverage', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    updateAgeMock.mockReturnValue(null);
    checkDeathMock.mockReturnValue(false);
    updateDeathChanceMock.mockReturnValue(0);
    getIntradayActivityMock.mockImplementation((_player, person) => person);
    promptNextMock.mockResolvedValue(null);
  });

  it('wraps dayOfYear back to 1 after day 365', () => {
    const player = createPlayer();
    const session = createSession(player);

    player.hourOfDay = 23;
    player.minuteOfHour = 59;
    player.dayOfYear = 365;
    player.dayOfWeek = 3;

    (session as any).advanceMinute();

    expect(player.dayOfYear).toBe(1);
  });

  it('applies hourly depletion and clamps energy-derived stats', () => {
    const player = createPlayer();
    const session = createSession(player);

    player.c.hunger = 98;
    player.c.thirst = 99;
    player.c.energy = 1;
    player.c.peakEnergy = 10;
    player.c.calcEnergy = 5;

    (session as any).processHourTick();

    expect(player.c.hunger).toBe(100);
    expect(player.c.thirst).toBe(100);
    // Energy no longer drains passively — only from actions/events
    expect(player.c.energy).toBe(1);
    expect(player.c.calcEnergy).toBe(0);
    expect(getIntradayActivityMock).toHaveBeenCalledWith(player, player.c);
  });

  it('uses character energy (not energyLevel), runs death checks, and auto-saves daily', () => {
    const player = createPlayer();
    const session = createSession(player);
    const saveSpy = vi.spyOn(session, 'savePlayer').mockResolvedValue();

    player.c.energy = 80;

    (session as any).processDayTick();

    // processDayTick runs death checks (updateAge is in processHourTick)
    expect(updateDeathChanceMock).toHaveBeenCalledWith(player.c);
    expect(checkDeathMock).toHaveBeenCalledWith(player.c);
    // Overnight sleep recovery: 80 + 25 = 105, capped at 100
    expect(player.c.energy).toBe(100);
    expect(saveSpy).toHaveBeenCalledTimes(1);
  });

  it('increments habit quit progress on daily tick and sends playerObject', () => {
    const player = createPlayer();
    player.c.habits = [
      {
        name: 'smoking',
        description: 'Test habit',
        type: 'negative',
        habitType: 'negative',
        status: 'quitting',
        quitProgress: 4,
      },
    ];
    const session = createSession(player);
    vi.spyOn(session, 'savePlayer').mockResolvedValue();
    const sendPlayerObjectSpy = vi
      .spyOn(session, 'sendPlayerObject')
      .mockImplementation(() => undefined);

    (session as any).processDayTick();

    expect(player.c.habits?.[0]?.quitProgress).toBe(5);
    expect(sendPlayerObjectSpy).toHaveBeenCalled();
  });

  it('marks the character dead when daily death check passes', () => {
    const player = createPlayer();
    const session = createSession(player);
    vi.spyOn(session, 'savePlayer').mockResolvedValue();
    checkDeathMock.mockReturnValue(true);

    (session as any).processDayTick();

    expect(player.c.status).toBe('dead');
  });
});
