/**
 * School Enrollment Dedup Tests
 *
 * Covers the data-duplication bug where school-age players accumulated many
 * school activities + education records (one per random school) because
 * setEducation() was called repeatedly without reusing the existing enrollment.
 *
 * Two units under test:
 *  - dedupeSchoolEnrollments(person): migration that collapses duplicate school
 *    activities down to the single current enrollment and removes stale/orphaned
 *    education records.
 *  - setEducation(player, person): must be idempotent — repeated calls must not
 *    add additional school activities or education records.
 */
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
  dedupeSchoolEnrollments,
  setEducation,
  ensureEducationSetup,
  clearEducationCaches,
  getHighSchools,
} from '../../../src/services/education/education_manager.js';
import { Person } from '../../../src/models/Person.js';
import { Player } from '../../../src/models/Player.js';

const SCHOOL_TYPES = ['elementary_school', 'high_school', 'college'];

function makeStudent(overrides: Record<string, unknown> = {}): Person {
  const person = new Person({
    id: 'student-1',
    firstname: 'Test',
    lastname: 'Student',
    sex: 'Female',
    ageYears: 15,
    ageDays: 15 * 365,
  });
  person.activities = [];
  person.activityRecords = [];
  Object.assign(person, overrides);
  return person;
}

function schoolActivities(person: Person) {
  return (person.activities ?? []).filter((a: { type?: string }) =>
    SCHOOL_TYPES.includes(a?.type ?? '')
  );
}

function educationRecords(person: Person) {
  return (person.activityRecords ?? []).filter(
    (r: { locationType?: string; locationId?: string }) =>
      SCHOOL_TYPES.includes(r?.locationType ?? '') || r?.locationId != null
  );
}

describe('dedupeSchoolEnrollments', () => {
  it('collapses many duplicate high-school enrollments down to the current one', () => {
    // Mirrors production player 5B979936...: many high_school activities (mix of
    // exact-id dups and different schools), one education record per enrollment,
    // records linked to activities by locationId (record.id !== locationId).
    const person = makeStudent({
      activities: [
        { id: 'hs-A', type: 'high_school', title: 'Valley High School' },
        { id: 'hs-A', type: 'high_school', title: 'Valley High School' }, // exact dup
        { id: 'hs-B', type: 'high_school', title: 'Northview High' },
        { id: 'hs-C', type: 'high_school', title: 'Prestige Prep School' },
        { id: 'ec-1', type: 'extracurricular', title: 'Choir' },
      ],
      activityRecords: [
        { id: 'rec-A1', locationId: 'hs-A', locationType: 'high_school', educationLevel: '10th', gpa: 50, focus: 'Balanced' },
        { id: 'rec-A2', locationId: 'hs-A', locationType: 'high_school', educationLevel: '10th', gpa: 50, focus: 'Balanced' },
        { id: 'rec-B', locationId: 'hs-B', locationType: 'high_school', educationLevel: '10th', gpa: 50, focus: 'Balanced' },
        { id: 'rec-C', locationId: 'hs-C', locationType: 'high_school', educationLevel: '10th', gpa: 50, focus: 'Balanced' },
        { id: 'ec-rec-1', type: 'extracurricular', focus: 'Work Hard', date: '2026-01-01' },
      ],
      current_education: { id: 'rec-C', locationId: 'hs-C', locationType: 'high_school', educationLevel: '10th' },
      high_school: { id: 'hs-C', title: 'Prestige Prep School' },
    });

    const changed = dedupeSchoolEnrollments(person);

    expect(changed).toBe(true);

    const schools = schoolActivities(person);
    expect(schools).toHaveLength(1);
    expect(schools[0].id).toBe('hs-C');

    const eduRecs = educationRecords(person);
    expect(eduRecs).toHaveLength(1);
    expect(eduRecs[0].locationId).toBe('hs-C');

    // Extracurricular activity + record must be preserved.
    expect((person.activities ?? []).filter((a: { type?: string }) => a.type === 'extracurricular')).toHaveLength(1);
    expect((person.activityRecords ?? []).filter((r: { type?: string }) => r.type === 'extracurricular')).toHaveLength(1);

    // current_education + high_school remain the kept enrollment.
    expect(person.current_education.locationId).toBe('hs-C');
    expect(person.high_school.id).toBe('hs-C');
  });

  it('removes orphaned education records whose locationId matches no activity', () => {
    const person = makeStudent({
      activities: [{ id: 'hs-C', type: 'high_school', title: 'Prestige Prep School' }],
      activityRecords: [
        { id: 'rec-gone', locationId: 'hs-GONE', locationType: 'high_school', educationLevel: '10th', gpa: 50 },
        { id: 'rec-C', locationId: 'hs-C', locationType: 'high_school', educationLevel: '10th', gpa: 50 },
      ],
      current_education: { id: 'rec-C', locationId: 'hs-C', locationType: 'high_school' },
      high_school: { id: 'hs-C', title: 'Prestige Prep School' },
    });

    const changed = dedupeSchoolEnrollments(person);

    expect(changed).toBe(true);
    const eduRecs = educationRecords(person);
    expect(eduRecs).toHaveLength(1);
    expect(eduRecs[0].locationId).toBe('hs-C');
  });

  it('is a no-op (returns false) when there is already exactly one clean enrollment', () => {
    const person = makeStudent({
      activities: [{ id: 'hs-C', type: 'high_school', title: 'Prestige Prep School' }],
      activityRecords: [
        { id: 'rec-C', locationId: 'hs-C', locationType: 'high_school', educationLevel: '10th', gpa: 50 },
      ],
      current_education: { id: 'rec-C', locationId: 'hs-C', locationType: 'high_school' },
      high_school: { id: 'hs-C', title: 'Prestige Prep School' },
    });

    const before = JSON.stringify(person.toJSON());
    const changed = dedupeSchoolEnrollments(person);

    expect(changed).toBe(false);
    expect(JSON.stringify(person.toJSON())).toBe(before);
  });

  it('preserves a single elementary + single high-school (does not collapse across types)', () => {
    const person = makeStudent({
      activities: [
        { id: 'el-1', type: 'elementary_school', title: 'Maple Elementary' },
        { id: 'hs-1', type: 'high_school', title: 'Valley High School' },
      ],
      activityRecords: [
        { id: 'rec-el', locationId: 'el-1', locationType: 'elementary_school', educationLevel: '5th', gpa: 70 },
        { id: 'rec-hs', locationId: 'hs-1', locationType: 'high_school', educationLevel: '10th', gpa: 60 },
      ],
      current_education: { id: 'rec-hs', locationId: 'hs-1', locationType: 'high_school' },
      high_school: { id: 'hs-1', title: 'Valley High School' },
      elementary_school: { id: 'el-1', title: 'Maple Elementary' },
    });

    const changed = dedupeSchoolEnrollments(person);

    expect(changed).toBe(false);
    expect(schoolActivities(person)).toHaveLength(2);
    expect(educationRecords(person)).toHaveLength(2);
  });

  it('repoints dangling current_education and high_school to the kept activity', () => {
    const person = makeStudent({
      activities: [{ id: 'hs-A', type: 'high_school', title: 'Valley High School' }],
      activityRecords: [
        { id: 'rec-A', locationId: 'hs-A', locationType: 'high_school', educationLevel: '10th', gpa: 50 },
      ],
      // Both refs point at a school that no longer exists as an activity.
      current_education: { id: 'rec-gone', locationId: 'hs-GONE', locationType: 'high_school' },
      high_school: { id: 'hs-GONE', title: 'Ghost High' },
    });

    const changed = dedupeSchoolEnrollments(person);

    expect(changed).toBe(true);
    expect(person.current_education.locationId).toBe('hs-A');
    expect(person.high_school.id).toBe('hs-A');
  });

  it('keeps exactly one record for the kept school, preferring the current_education record', () => {
    const person = makeStudent({
      activities: [{ id: 'hs-C', type: 'high_school', title: 'Prestige Prep School' }],
      activityRecords: [
        { id: 'rec-C1', locationId: 'hs-C', locationType: 'high_school', educationLevel: '10th', gpa: 50 },
        { id: 'rec-C2', locationId: 'hs-C', locationType: 'high_school', educationLevel: '10th', gpa: 88 },
      ],
      current_education: { id: 'rec-C2', locationId: 'hs-C', locationType: 'high_school' },
      high_school: { id: 'hs-C', title: 'Prestige Prep School' },
    });

    const changed = dedupeSchoolEnrollments(person);

    expect(changed).toBe(true);
    const eduRecs = educationRecords(person);
    expect(eduRecs).toHaveLength(1);
    expect(eduRecs[0].id).toBe('rec-C2');
  });
});

describe('setEducation idempotency', () => {
  beforeEach(() => {
    clearEducationCaches();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('does not add a second school or record when called repeatedly', () => {
    const hs = getHighSchools();
    const firstSchoolId = hs[0].id;

    // Force the first enrollment to pick index 0; any later random would pick a
    // different school (index 5) — proving the second call must reuse, not re-add.
    const rnd = vi.spyOn(Math, 'random');
    rnd.mockReturnValueOnce(0);
    rnd.mockReturnValue(0.9);

    const person = makeStudent();
    const player = { date: '2026-01-15', userId: 'u1', c: person } as unknown as Player;

    setEducation(player, person);
    setEducation(player, person);

    expect(schoolActivities(person)).toHaveLength(1);
    expect(schoolActivities(person)[0].id).toBe(firstSchoolId);
    expect(educationRecords(person)).toHaveLength(1);
    expect(person.high_school.id).toBe(firstSchoolId);
    expect(person.current_education.locationId).toBe(firstSchoolId);
    expect(person.occupation).toBe('student');
  });

  it('reuses an existing high-school enrollment instead of replacing it', () => {
    const person = makeStudent({
      activities: [{ id: 'hs-existing', type: 'high_school', title: 'Existing High' }],
      activityRecords: [
        { id: 'rec-existing', locationId: 'hs-existing', locationType: 'high_school', educationLevel: '10th', gpa: 64, focus: 'Work Hard' },
      ],
    });
    const player = { date: '2026-01-15', userId: 'u1', c: person } as unknown as Player;

    setEducation(player, person);

    expect(schoolActivities(person)).toHaveLength(1);
    expect(schoolActivities(person)[0].id).toBe('hs-existing');
    expect(person.high_school.id).toBe('hs-existing');
    expect(educationRecords(person)).toHaveLength(1);
    expect(person.current_education.locationId).toBe('hs-existing');
  });
});

describe('setEducation college enrollment', () => {
  beforeEach(() => {
    clearEducationCaches();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('enrolls a newly created 18-year-old in college (no "work" fallback)', () => {
    // 0.1 is a roll that previously meant "work" (ratio < 0.4); a new 18yo must
    // now still enroll in college regardless of the roll.
    vi.spyOn(Math, 'random').mockReturnValue(0.1);

    const person = makeStudent({ ageYears: 18, ageDays: 18 * 365, activities: [], activityRecords: [] });
    const player = { date: '2026-01-15', userId: 'u1', c: person } as unknown as Player;

    setEducation(player, person);

    expect(person.occupation).toBe('student');
    const schools = schoolActivities(person);
    expect(schools).toHaveLength(1);
    expect(schools[0].type).toBe('college');
    expect(person.college).toBeTruthy();
    expect(person.current_education).toBeTruthy();
    expect(person.current_education.locationType).toBe('college');
    expect(person.education).toBe('college yr 1');
    expect(educationRecords(person)).toHaveLength(1);
  });
});

describe('ensureEducationSetup wiring', () => {
  it('collapses duplicate school enrollments on the load path', () => {
    const person = makeStudent({
      activities: [
        { id: 'hs-A', type: 'high_school', title: 'Valley High School' },
        { id: 'hs-B', type: 'high_school', title: 'Northview High' },
        { id: 'hs-C', type: 'high_school', title: 'Prestige Prep School' },
      ],
      activityRecords: [
        { id: 'rec-A', locationId: 'hs-A', locationType: 'high_school', educationLevel: '10th', gpa: 50 },
        { id: 'rec-B', locationId: 'hs-B', locationType: 'high_school', educationLevel: '10th', gpa: 50 },
        { id: 'rec-C', locationId: 'hs-C', locationType: 'high_school', educationLevel: '10th', gpa: 50 },
      ],
      occupation: 'student',
      current_education: { id: 'rec-C', locationId: 'hs-C', locationType: 'high_school' },
      high_school: { id: 'hs-C', title: 'Prestige Prep School' },
    });
    // A classmate already exists so ensureEducationSetup won't try to create more.
    const player = {
      date: '2026-01-15',
      userId: 'u1',
      c: person,
      r: [{ relationships: ['classmate'] }],
    } as unknown as Player;

    const changed = ensureEducationSetup(player, person);

    expect(changed).toBe(true);
    expect(schoolActivities(person)).toHaveLength(1);
    expect(schoolActivities(person)[0].id).toBe('hs-C');
    expect(educationRecords(person)).toHaveLength(1);
  });
});
