/**
 * Education Service Tests
 * Tests the education business logic with mock implementations
 */
import { describe, it, expect, beforeEach } from 'vitest';
import {
  handleEducation,
  setExtracurricular,
  getFocuses,
  getFocus,
  getRandomFocus,
  updateFocus,
  getExtracurriculars as getRealExtracurriculars,
  getEducationLevelForAge,
} from '../../../src/services/education/education_manager.js';
import { Person } from '../../../src/models/Person.js';
import { Player } from '../../../src/models/Player.js';

// Mock extracurricular data
interface Extracurricular {
  id: string;
  title: string;
  category: string;
  energyCost?: number;
  moneyCost?: number;
  intelligenceBonus?: number;
  healthBonus?: number;
  happinessBonus?: number;
  prestigeBonus?: number;
  minAge?: number;
  maxAge?: number;
}

const EXTRACURRICULARS: Extracurricular[] = [
  { id: 'ec-1', title: 'Piano Lessons', category: 'music', energyCost: 15, moneyCost: 100, intelligenceBonus: 5, happinessBonus: 3 },
  { id: 'ec-2', title: 'Guitar Lessons', category: 'music', energyCost: 15, moneyCost: 80, happinessBonus: 5 },
  { id: 'ec-3', title: 'Soccer Team', category: 'sports', energyCost: 25, healthBonus: 10, happinessBonus: 5 },
  { id: 'ec-4', title: 'Basketball Team', category: 'sports', energyCost: 25, healthBonus: 10, happinessBonus: 5 },
  { id: 'ec-5', title: 'Swimming Lessons', category: 'sports', energyCost: 20, moneyCost: 50, healthBonus: 15 },
  { id: 'ec-6', title: 'Drama Club', category: 'arts', energyCost: 15, happinessBonus: 10, prestigeBonus: 5 },
  { id: 'ec-7', title: 'Dance Class', category: 'arts', energyCost: 20, moneyCost: 75, healthBonus: 5, happinessBonus: 8 },
  { id: 'ec-8', title: 'Art Class', category: 'arts', energyCost: 10, moneyCost: 60, intelligenceBonus: 3, happinessBonus: 5 },
  { id: 'ec-9', title: 'Chess Club', category: 'academic', energyCost: 5, intelligenceBonus: 10 },
  { id: 'ec-10', title: 'Science Club', category: 'academic', energyCost: 10, intelligenceBonus: 8 },
];

const SCHOOL_CLASSES = {
  elementary: ['Math', 'Reading', 'Writing', 'Science', 'Art', 'PE', 'Music'],
  middleSchool: ['Math', 'English', 'Science', 'History', 'PE', 'Art', 'Foreign Language'],
  highSchool: ['Algebra', 'English', 'Biology', 'History', 'PE', 'Electives'],
};

// Mock person type
interface MockPerson {
  id: string;
  firstname: string;
  ageYears: number;
  education: string;
  activities: Array<{ id: string; title: string }>;
}

// Mock service functions
function getExtracurriculars(): Extracurricular[] {
  return [...EXTRACURRICULARS];
}

function getExtracurricularByTitle(title: string): Extracurricular | undefined {
  return EXTRACURRICULARS.find(e =>
    e.title.toLowerCase() === title.toLowerCase()
  );
}

function applyForExtracurricular(person: MockPerson, activityId: string): boolean {
  const activity = EXTRACURRICULARS.find(e => e.id === activityId);
  if (!activity) return false;

  // Check if already enrolled
  if (person.activities.some(a => a.id === activityId)) {
    return false;
  }

  person.activities.push({ id: activity.id, title: activity.title });
  return true;
}

function quitExtracurricular(person: MockPerson, activityId: string): boolean {
  const index = person.activities.findIndex(a => a.id === activityId);
  if (index === -1) return false;

  person.activities.splice(index, 1);
  return true;
}

function getSchoolClasses(age: number): string[] {
  if (age < 11) return SCHOOL_CLASSES.elementary;
  if (age < 14) return SCHOOL_CLASSES.middleSchool;
  return SCHOOL_CLASSES.highSchool;
}

function enrollInSchool(person: MockPerson): boolean {
  if (person.ageYears < 5) return false;
  if (person.education !== 'None') return true; // Already enrolled

  if (person.ageYears < 11) {
    person.education = 'Elementary School';
  } else if (person.ageYears < 14) {
    person.education = 'Middle School';
  } else if (person.ageYears < 18) {
    person.education = 'High School';
  } else {
    person.education = 'College';
  }

  return true;
}

describe('Education Service', () => {
  let person: MockPerson;

  beforeEach(() => {
    person = {
      id: 'player-1',
      firstname: 'Test',
      ageYears: 10,
      education: 'None',
      activities: [],
    };
  });

  describe('getExtracurriculars', () => {
    it('should return a list of extracurricular activities', () => {
      const extracurriculars = getExtracurriculars();

      expect(Array.isArray(extracurriculars)).toBe(true);
      expect(extracurriculars.length).toBeGreaterThan(0);
    });

    it('should include common extracurricular activities', () => {
      const extracurriculars = getExtracurriculars();
      const titles = extracurriculars.map(e => e.title.toLowerCase());

      const commonActivities = ['piano', 'guitar', 'soccer', 'basketball', 'drama'];
      const foundActivities = commonActivities.filter(a =>
        titles.some(t => t.includes(a))
      );

      expect(foundActivities.length).toBeGreaterThan(0);
    });

    it('should have required properties for each extracurricular', () => {
      const extracurriculars = getExtracurriculars();

      extracurriculars.forEach(activity => {
        expect(activity).toHaveProperty('id');
        expect(activity).toHaveProperty('title');
        expect(typeof activity.id).toBe('string');
        expect(typeof activity.title).toBe('string');
      });
    });
  });

  describe('getExtracurricularByTitle', () => {
    it('should find extracurricular by exact title', () => {
      const extracurriculars = getExtracurriculars();
      const first = extracurriculars[0];
      const found = getExtracurricularByTitle(first.title);

      expect(found).toBeDefined();
      expect(found?.title).toBe(first.title);
    });

    it('should be case-insensitive', () => {
      const extracurriculars = getExtracurriculars();
      const first = extracurriculars[0];
      const found = getExtracurricularByTitle(first.title.toUpperCase());

      expect(found).toBeDefined();
    });

    it('should return undefined for non-existent activity', () => {
      const found = getExtracurricularByTitle('NonExistentActivity123');
      expect(found).toBeUndefined();
    });
  });

  describe('applyForExtracurricular', () => {
    it('should enroll player in extracurricular', () => {
      const extracurriculars = getExtracurriculars();
      const result = applyForExtracurricular(person, extracurriculars[0].id);

      expect(result).toBe(true);
    });

    it('should not allow duplicate enrollment', () => {
      const extracurriculars = getExtracurriculars();

      // First enrollment should succeed
      applyForExtracurricular(person, extracurriculars[0].id);

      // Second enrollment should fail
      const result = applyForExtracurricular(person, extracurriculars[0].id);
      expect(result).toBe(false);
    });

    it('should fail for invalid activity ID', () => {
      const result = applyForExtracurricular(person, 'invalid-id-12345');
      expect(result).toBe(false);
    });

    it('should add activity to person activities list', () => {
      const extracurriculars = getExtracurriculars();
      applyForExtracurricular(person, extracurriculars[0].id);

      expect(person.activities).toHaveLength(1);
      expect(person.activities[0].id).toBe(extracurriculars[0].id);
    });
  });

  describe('quitExtracurricular', () => {
    it('should remove player from extracurricular', () => {
      const extracurriculars = getExtracurriculars();
      person.activities = [{ id: extracurriculars[0].id, title: extracurriculars[0].title }];

      const result = quitExtracurricular(person, extracurriculars[0].id);
      expect(result).toBe(true);
      expect(person.activities).toHaveLength(0);
    });

    it('should fail if not enrolled', () => {
      const result = quitExtracurricular(person, 'some-activity-id');
      expect(result).toBe(false);
    });
  });

  describe('getSchoolClasses', () => {
    it('should return elementary school classes for young children', () => {
      const classes = getSchoolClasses(8);

      expect(Array.isArray(classes)).toBe(true);
      expect(classes.length).toBeGreaterThan(0);
    });

    it('should return high school classes for teenagers', () => {
      const classes = getSchoolClasses(15);

      expect(Array.isArray(classes)).toBe(true);
      expect(classes.length).toBeGreaterThan(0);
    });

    it('should return different classes for different ages', () => {
      const elementaryClasses = getSchoolClasses(8);
      const highSchoolClasses = getSchoolClasses(16);

      expect(elementaryClasses).toBeDefined();
      expect(highSchoolClasses).toBeDefined();
    });

    it('should return middle school classes for pre-teens', () => {
      const classes = getSchoolClasses(12);
      expect(classes).toBeDefined();
      expect(classes.length).toBeGreaterThan(0);
    });
  });

  describe('enrollInSchool', () => {
    it('should enroll eligible player in school', () => {
      person.ageYears = 6;
      person.education = 'None';

      const result = enrollInSchool(person);

      expect(result).toBe(true);
      expect(person.education).not.toBe('None');
    });

    it('should not enroll player already in school', () => {
      person.ageYears = 10;
      person.education = 'Elementary School';

      const result = enrollInSchool(person);
      expect(result).toBe(true); // Returns true because already enrolled
    });

    it('should handle too young for school', () => {
      person.ageYears = 3;
      person.education = 'None';

      const result = enrollInSchool(person);
      expect(result).toBe(false);
      expect(person.education).toBe('None');
    });

    it('should enroll in correct level based on age', () => {
      person.ageYears = 15;
      person.education = 'None';

      enrollInSchool(person);
      expect(person.education).toBe('High School');
    });
  });

  describe('education levels', () => {
    const educationLevels = [
      { level: 'Elementary School', minAge: 5, maxAge: 11 },
      { level: 'Middle School', minAge: 11, maxAge: 14 },
      { level: 'High School', minAge: 14, maxAge: 18 },
      { level: 'College', minAge: 18, maxAge: 22 },
    ];

    educationLevels.forEach(({ level, minAge }) => {
      it(`should handle ${level} education level`, () => {
        person.ageYears = minAge;
        person.education = level;

        expect(person.education).toBe(level);
        expect(person.ageYears).toBeGreaterThanOrEqual(minAge);
      });
    });
  });

  describe('extracurricular categories', () => {
    it('should have sports activities', () => {
      const extracurriculars = getExtracurriculars();
      const sports = extracurriculars.filter(e => e.category === 'sports');

      expect(sports.length).toBeGreaterThan(0);
    });

    it('should have music activities', () => {
      const extracurriculars = getExtracurriculars();
      const music = extracurriculars.filter(e => e.category === 'music');

      expect(music.length).toBeGreaterThan(0);
    });

    it('should have arts activities', () => {
      const extracurriculars = getExtracurriculars();
      const arts = extracurriculars.filter(e => e.category === 'arts');

      expect(arts.length).toBeGreaterThan(0);
    });

    it('should have academic activities', () => {
      const extracurriculars = getExtracurriculars();
      const academic = extracurriculars.filter(e => e.category === 'academic');

      expect(academic.length).toBeGreaterThan(0);
    });
  });

  describe('activity requirements', () => {
    it('should have activities with energy costs', () => {
      const extracurriculars = getExtracurriculars();

      extracurriculars.forEach(activity => {
        if (activity.energyCost !== undefined) {
          expect(activity.energyCost).toBeGreaterThanOrEqual(0);
        }
      });
    });

    it('should have activities with stat benefits', () => {
      const extracurriculars = getExtracurriculars();
      const withBenefits = extracurriculars.filter(a =>
        (a.intelligenceBonus ?? 0) > 0 ||
        (a.healthBonus ?? 0) > 0 ||
        (a.happinessBonus ?? 0) > 0 ||
        (a.prestigeBonus ?? 0) > 0
      );

      expect(withBenefits.length).toBeGreaterThan(0);
    });
  });

  describe('multiple activities', () => {
    it('should allow enrollment in multiple activities', () => {
      const extracurriculars = getExtracurriculars();

      applyForExtracurricular(person, extracurriculars[0].id);
      applyForExtracurricular(person, extracurriculars[1].id);
      applyForExtracurricular(person, extracurriculars[2].id);

      expect(person.activities).toHaveLength(3);
    });

    it('should quit specific activity without affecting others', () => {
      const extracurriculars = getExtracurriculars();

      applyForExtracurricular(person, extracurriculars[0].id);
      applyForExtracurricular(person, extracurriculars[1].id);

      quitExtracurricular(person, extracurriculars[0].id);

      expect(person.activities).toHaveLength(1);
      expect(person.activities[0].id).toBe(extracurriculars[1].id);
    });
  });
});

/**
 * Tests for the actual education_manager.ts implementations
 * Focus system and GPA tracking
 */
describe('Education Manager (Ported from Python)', () => {
  describe('Focus System', () => {
    it('should return all focus types', () => {
      const focuses = getFocuses();

      expect(focuses).toHaveLength(4);
      expect(focuses.map(f => f.focus_name)).toContain('Work Hard');
      expect(focuses.map(f => f.focus_name)).toContain('Slack Off');
      expect(focuses.map(f => f.focus_name)).toContain('Socialize');
      expect(focuses.map(f => f.focus_name)).toContain('Balanced');
    });

    it('should get focus by name', () => {
      const workHard = getFocus('Work Hard');
      expect(workHard).toBeDefined();
      expect(workHard?.focus_name).toBe('Work Hard');
      expect(workHard?.energyModifier).toBe(10);

      const slackOff = getFocus('Slack Off');
      expect(slackOff).toBeDefined();
      expect(slackOff?.focus_name).toBe('Slack Off');
      expect(slackOff?.energyModifier).toBe(-10);

      const balanced = getFocus('Balanced');
      expect(balanced).toBeDefined();
      expect(balanced?.energyModifier).toBe(0);
    });

    it('should return undefined for non-existent focus', () => {
      const focus = getFocus('NonExistent');
      expect(focus).toBeUndefined();
    });

    it('should return a random focus', () => {
      const focus = getRandomFocus();
      expect(focus).toBeDefined();
      expect(focus.focus_name).toBeDefined();
      expect(['Work Hard', 'Slack Off', 'Socialize', 'Balanced']).toContain(focus.focus_name);
    });

    it('should have correct energy modifiers', () => {
      const focuses = getFocuses();
      const workHard = focuses.find(f => f.focus_name === 'Work Hard');
      const slackOff = focuses.find(f => f.focus_name === 'Slack Off');
      const socialize = focuses.find(f => f.focus_name === 'Socialize');
      const balanced = focuses.find(f => f.focus_name === 'Balanced');

      expect(workHard?.energyModifier).toBe(10);  // More energy used
      expect(slackOff?.energyModifier).toBe(-10); // Less energy used
      expect(socialize?.energyModifier).toBe(10); // More energy used
      expect(balanced?.energyModifier).toBe(0);   // Neutral
    });
  });

  describe('handleEducation - GPA Updates', () => {
    let testPerson: Person;

    beforeEach(() => {
      testPerson = new Person({
        id: 'test-student-1',
        firstname: 'Test',
        lastname: 'Student',
        sex: 'Male',
        ageYears: 10,
        ageDays: 3650,
      });
    });

    it('should not update GPA if no current_education', () => {
      testPerson.current_education = undefined;
      testPerson.activityRecords = [{ id: 'record-1', type: 'education', gpa: 50, focus: 'Balanced' }];

      handleEducation(testPerson);

      // GPA should remain unchanged
      expect(testPerson.activityRecords[0].gpa).toBe(50);
    });

    it('should update GPA based on focus - Work Hard', () => {
      const recordId = 'edu-record-1';
      testPerson.current_education = { id: recordId, focus: 'Work Hard' };
      testPerson.activityRecords = [{ id: recordId, type: 'education', gpa: 50, focus: 'Work Hard' }];

      // Run multiple times to check GPA changes
      const initialGPA = testPerson.activityRecords[0].gpa;
      for (let i = 0; i < 100; i++) {
        handleEducation(testPerson);
      }

      // With Work Hard, GPA should generally increase (random but biased up)
      // The random range is -1 to 2, so average change is 0.5
      expect(testPerson.activityRecords[0].gpa).toBeGreaterThanOrEqual(0);
      expect(testPerson.activityRecords[0].gpa).toBeLessThanOrEqual(100);
    });

    it('should update GPA based on focus - Slack Off', () => {
      const recordId = 'edu-record-2';
      testPerson.current_education = { id: recordId, focus: 'Slack Off' };
      testPerson.activityRecords = [{ id: recordId, type: 'education', gpa: 50, focus: 'Slack Off' }];

      // Run multiple times
      for (let i = 0; i < 100; i++) {
        handleEducation(testPerson);
      }

      // With Slack Off, GPA should generally decrease (random -1 to 0)
      expect(testPerson.activityRecords[0].gpa).toBeGreaterThanOrEqual(0);
      expect(testPerson.activityRecords[0].gpa).toBeLessThanOrEqual(100);
    });

    it('should clamp GPA between 0 and 100', () => {
      const recordId = 'edu-record-3';

      // Test upper bound
      testPerson.current_education = { id: recordId, focus: 'Work Hard' };
      testPerson.activityRecords = [{ id: recordId, type: 'education', gpa: 99, focus: 'Work Hard' }];

      for (let i = 0; i < 20; i++) {
        handleEducation(testPerson);
      }
      expect(testPerson.activityRecords[0].gpa).toBeLessThanOrEqual(100);

      // Test lower bound
      testPerson.activityRecords[0].gpa = 1;
      testPerson.current_education.focus = 'Slack Off';
      testPerson.activityRecords[0].focus = 'Slack Off';

      for (let i = 0; i < 20; i++) {
        handleEducation(testPerson);
      }
      expect(testPerson.activityRecords[0].gpa).toBeGreaterThanOrEqual(0);
    });
  });

  describe('setExtracurricular', () => {
    let testPerson: Person;

    beforeEach(() => {
      testPerson = new Person({
        id: 'test-student-2',
        firstname: 'Test',
        lastname: 'Student',
        sex: 'Female',
        ageYears: 12,
        ageDays: 4380,
      });
      testPerson.activities = [];
      testPerson.activityRecords = [];
    });

    it('should add extracurricular to activities', () => {
      const realExtracurriculars = getRealExtracurriculars();
      const choir = realExtracurriculars[0]; // Choir

      setExtracurricular(testPerson, choir, '2024-01-15');

      expect(testPerson.activities).toHaveLength(1);
      expect(testPerson.activities[0].id).toBe(choir.id);
      expect(testPerson.activities[0].title).toBe(choir.title);
    });

    it('should create activity record with correct focus', () => {
      const realExtracurriculars = getRealExtracurriculars();
      const choir = realExtracurriculars[0]; // Choir has 'Work Hard' focus

      setExtracurricular(testPerson, choir, '2024-01-15');

      expect(testPerson.activityRecords).toHaveLength(1);
      expect(testPerson.activityRecords[0].id).toBe(choir.id);
      expect(testPerson.activityRecords[0].type).toBe('extracurricular');
      expect(testPerson.activityRecords[0].focus).toBe(choir.focus);
      expect(testPerson.activityRecords[0].date).toBe('2024-01-15');
    });

    it('should initialize empty activities array if not present', () => {
      testPerson.activities = undefined as any;
      testPerson.activityRecords = undefined as any;

      const realExtracurriculars = getRealExtracurriculars();
      setExtracurricular(testPerson, realExtracurriculars[0], '2024-01-15');

      expect(testPerson.activities).toBeDefined();
      expect(testPerson.activityRecords).toBeDefined();
      expect(testPerson.activities).toHaveLength(1);
      expect(testPerson.activityRecords).toHaveLength(1);
    });
  });

  describe('updateFocus', () => {
    it('should update activity focus', () => {
      const testPlayer = {
        c: new Person({
          id: 'focus-test-1',
          firstname: 'Focus',
          lastname: 'Test',
          sex: 'Male',
          ageYears: 15,
          ageDays: 5475,
        }),
      } as Player;

      testPlayer.c.activityRecords = [
        { id: 'activity-1', type: 'education', focus: 'Balanced' },
      ];

      const result = updateFocus(testPlayer, 'activity-1', 'Work Hard');

      expect(result).toBe(true);
      expect(testPlayer.c.activityRecords[0].focus).toBe('Work Hard');
    });

    it('should return false for non-existent focus name', () => {
      const testPlayer = {
        c: new Person({
          id: 'focus-test-2',
          firstname: 'Focus',
          lastname: 'Test',
          sex: 'Female',
          ageYears: 16,
          ageDays: 5840,
        }),
      } as Player;

      testPlayer.c.activityRecords = [
        { id: 'activity-2', type: 'education', focus: 'Balanced' },
      ];

      const result = updateFocus(testPlayer, 'activity-2', 'NonExistentFocus');

      expect(result).toBe(false);
      expect(testPlayer.c.activityRecords[0].focus).toBe('Balanced');
    });

    it('should return false for non-existent activity', () => {
      const testPlayer = {
        c: new Person({
          id: 'focus-test-3',
          firstname: 'Focus',
          lastname: 'Test',
          sex: 'Male',
          ageYears: 14,
          ageDays: 5110,
        }),
      } as Player;

      testPlayer.c.activityRecords = [
        { id: 'activity-3', type: 'education', focus: 'Balanced' },
      ];

      const result = updateFocus(testPlayer, 'non-existent-activity', 'Work Hard');

      expect(result).toBe(false);
    });
  });

  describe('getEducationLevelForAge', () => {
    it('should return correct grade levels for each age', () => {
      expect(getEducationLevelForAge(5)).toBe('kindergarten');
      expect(getEducationLevelForAge(6)).toBe('1st');
      expect(getEducationLevelForAge(7)).toBe('2nd');
      expect(getEducationLevelForAge(8)).toBe('3rd');
      expect(getEducationLevelForAge(9)).toBe('4th');
      expect(getEducationLevelForAge(10)).toBe('5th');
      expect(getEducationLevelForAge(11)).toBe('6th');
      expect(getEducationLevelForAge(12)).toBe('7th');
      expect(getEducationLevelForAge(13)).toBe('8th');
      expect(getEducationLevelForAge(14)).toBe('9th');
      expect(getEducationLevelForAge(15)).toBe('10th');
      expect(getEducationLevelForAge(16)).toBe('11th');
      expect(getEducationLevelForAge(17)).toBe('12th');
    });

    it('should return null for ages outside school range', () => {
      expect(getEducationLevelForAge(4)).toBeNull();
      expect(getEducationLevelForAge(18)).toBeNull();
      expect(getEducationLevelForAge(25)).toBeNull();
    });
  });

  describe('Real Extracurriculars', () => {
    it('should have correct focus types', () => {
      const extracurriculars = getRealExtracurriculars();

      // Choir should have Work Hard focus
      const choir = extracurriculars.find(e => e.title === 'Choir');
      expect(choir?.focus).toBe('Work Hard');

      // Basketball should have Socialize focus
      const basketball = extracurriculars.find(e => e.title === 'Basketball');
      expect(basketball?.focus).toBe('Socialize');

      // Writing Club should have Balanced focus
      const writingClub = extracurriculars.find(e => e.title === 'Writing Club');
      expect(writingClub?.focus).toBe('Balanced');
    });

    it('should have energy modifiers', () => {
      const extracurriculars = getRealExtracurriculars();

      extracurriculars.forEach(activity => {
        expect(activity.energyModifier).toBeDefined();
        expect(activity.energyModifier).toBeGreaterThan(0);
      });
    });
  });
});
