/**
 * T010d — Romantic neglect-decay (replaces the old toward-50 homeostasis).
 *
 * An untended romance erodes once past the grace period and eventually
 * auto-breaks-up via the existing finalize/divorce path. A tended romance
 * (recent positive interaction) does NOT decay.
 */
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Person } from '../../../src/models/Person.js';
import { Player } from '../../../src/models/Player.js';

vi.mock('../../../src/services/retention/index.js', () => ({
  checkAchievementsAsync: vi.fn(async () => null),
  trackMarriage: vi.fn(),
  trackDating: vi.fn(),
  trackAffinity: vi.fn(),
  getPlayerStatistics: vi.fn(() => ({})),
}));

import {
  applyNeglectDecay,
  recordPositiveInteraction,
  createRelationship,
  getRelData,
  getActiveRelationship,
  gameDaysBetween,
  NEGLECT_GRACE_DAYS,
  NEGLECT_SCORE_DECAY_PER_DAY,
  BREAKUP_SCORE_THRESHOLD,
  BREAKUP_NEGLECT_DAYS,
} from '../../../src/services/relationships/relationship_manager.js';

/** Build a player with an active Dating relationship, partner affinity given. */
function makeCouple(opts: {
  status?: 'Dating' | 'Married';
  score?: number;
  affinity?: number;
  startDate?: string;
}): { player: Player; partner: Person } {
  const character = new Person({
    id: 'me',
    firstname: 'Alex',
    lastname: 'P',
    sex: 'Male',
    ageYears: 30,
  });
  const partner = new Person({
    id: 'partner-1',
    firstname: 'Taylor',
    lastname: 'Q',
    sex: 'Female',
    ageYears: 30,
    affinity: opts.affinity ?? 70,
    relationships: ['partner'],
  });
  const player = new Player({
    userId: 'u1',
    character,
    r: [partner],
    status: 'playing',
    date: '01-01',
  });
  const rel = createRelationship(character.id, partner.id, opts.startDate ?? '01-01', opts.status ?? 'Dating', 'together');
  rel.relationshipScore = opts.score ?? 60;
  player.relData.push(rel as unknown as (typeof player.relData)[number]);
  return { player, partner };
}

/** Advance a date string forward by N days in MM-DD form. */
function dateAfter(days: number, base = new Date(2001, 0, 1)): string {
  const d = new Date(base);
  d.setDate(d.getDate() + days);
  return `${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}

describe('gameDaysBetween', () => {
  it('counts in-game days between MM-DD dates', () => {
    expect(gameDaysBetween('01-01', '01-08')).toBe(7);
  });
  it('handles calendar wrap', () => {
    expect(gameDaysBetween('12-30', '01-02')).toBe(3);
  });
});

describe('applyNeglectDecay', () => {
  beforeEach(() => vi.clearAllMocks());

  it('does not decay a tended relationship within the grace period', () => {
    const { player, partner } = makeCouple({ score: 60 });
    recordPositiveInteraction(partner, '01-05'); // tended recently
    player.date = dateAfter(NEGLECT_GRACE_DAYS, new Date(2001, 0, 5)); // within grace

    const before = getRelData(player, partner.id)!.relationshipScore;
    const result = applyNeglectDecay(player, player.date);

    expect(result.decayed).toBe(false);
    expect(getRelData(player, partner.id)!.relationshipScore).toBe(before);
  });

  it('decays score and affinity once past the grace period', () => {
    const { player, partner } = makeCouple({ score: 60, affinity: 70 });
    partner.lastPositiveInteraction = '01-01';
    player.date = dateAfter(NEGLECT_GRACE_DAYS + 1); // just past grace

    const scoreBefore = getRelData(player, partner.id)!.relationshipScore;
    const affBefore = partner.affinity;
    const result = applyNeglectDecay(player, player.date);

    expect(result.decayed).toBe(true);
    expect(getRelData(player, partner.id)!.relationshipScore).toBe(scoreBefore - NEGLECT_SCORE_DECAY_PER_DAY);
    expect(partner.affinity).toBeLessThan(affBefore);
  });

  it('eventually auto-breaks-up a sustained-neglect dating relationship', () => {
    const { player, partner } = makeCouple({ status: 'Dating', score: BREAKUP_SCORE_THRESHOLD, affinity: 50 });
    partner.lastPositiveInteraction = '01-01';
    player.date = dateAfter(BREAKUP_NEGLECT_DAYS); // long neglect

    const result = applyNeglectDecay(player, player.date);

    expect(result.brokeUp).toBe(true);
    // Relationship removed via finalizeRelationship path.
    expect(getActiveRelationship(player)).toBeNull();
    expect(player.relData).toHaveLength(0);
    expect(player.messageQueue.some((m) => m.includes('broke up'))).toBe(true);
  });

  it('auto-divorces a sustained-neglect marriage', () => {
    const { player, partner } = makeCouple({ status: 'Married', score: BREAKUP_SCORE_THRESHOLD, affinity: 50 });
    partner.lastPositiveInteraction = '01-01';
    player.date = dateAfter(BREAKUP_NEGLECT_DAYS);

    const result = applyNeglectDecay(player, player.date);

    expect(result.brokeUp).toBe(true);
    expect(player.relData).toHaveLength(0);
    expect(player.messageQueue.some((m) => m.toLowerCase().includes('divorce'))).toBe(true);
  });

  it('returns no-op when there is no active relationship', () => {
    const character = new Person({ id: 'me', firstname: 'A', lastname: 'B', sex: 'Male', ageYears: 30 });
    const player = new Player({ userId: 'u', character, r: [], status: 'playing', date: '01-01' });
    const result = applyNeglectDecay(player, player.date);
    expect(result.decayed).toBe(false);
    expect(result.brokeUp).toBe(false);
  });

  it('recordPositiveInteraction resets the neglect clock so a tended romance never auto-ends', () => {
    const { player, partner } = makeCouple({ status: 'Dating', score: BREAKUP_SCORE_THRESHOLD + 5, affinity: 60 });
    partner.lastPositiveInteraction = '01-01';

    // Player keeps tending the relationship every few days for a long stretch.
    for (let day = 5; day <= BREAKUP_NEGLECT_DAYS + 30; day += 5) {
      const d = dateAfter(day);
      recordPositiveInteraction(partner, d);
      player.date = d;
      applyNeglectDecay(player, d);
    }

    expect(getActiveRelationship(player)).not.toBeNull();
    expect(player.relData).toHaveLength(1);
  });
});
