import { describe, it, expect, beforeEach } from 'vitest';
import { useManaStore } from '../stores/manaStore';
import { ELEMENTS, BASE_UNLOCKED_ELEMENTS } from '../constants/elements';

// ─── Regression test: Earth element desync after Unlock All ─────────────────
// https://gitea.tailf367e3.ts.net/Anexim/Mana-Loop/issues/338
// https://gitea.tailf367e3.ts.net/Anexim/Mana-Loop/issues/339

function resetManaStore() {
  useManaStore.setState({
    rawMana: 10,
    meditateTicks: 0,
    totalManaGathered: 0,
    elements: Object.fromEntries(
      Object.keys(ELEMENTS).map(k => [
        k,
        {
          current: 0,
          max: 10,
          baseMax: 10,
          unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
        },
      ])
    ) as Record<string, { current: number; max: number; baseMax: number; unlocked: boolean }>,
    elementRegen: {},
  });
}

/**
 * Simulate an old save that is missing some elements (e.g. from before
 * composite/exotic elements were added). Only has base + transference.
 */
function setOldSaveMissingElements() {
  const oldElements: Record<string, { current: number; max: number; baseMax: number; unlocked: boolean }> = {
    fire: { current: 0, max: 10, baseMax: 10, unlocked: false },
    water: { current: 0, max: 10, baseMax: 10, unlocked: false },
    air: { current: 0, max: 10, baseMax: 10, unlocked: false },
    earth: { current: 5, max: 10, baseMax: 10, unlocked: false },
    light: { current: 0, max: 10, baseMax: 10, unlocked: false },
    dark: { current: 0, max: 10, baseMax: 10, unlocked: false },
    death: { current: 0, max: 10, baseMax: 10, unlocked: false },
    transference: { current: 3, max: 10, baseMax: 10, unlocked: true },
  };
  useManaStore.setState({ elements: oldElements });
}

describe('Earth element desync after Unlock All', () => {
  beforeEach(() => {
    resetManaStore();
  });

  it('earth element exists in initial state', () => {
    const elements = useManaStore.getState().elements;
    expect(elements.earth).toBeDefined();
    expect(elements.earth.unlocked).toBe(false);
  });

  it('unlockElement unlocks earth correctly', () => {
    const result = useManaStore.getState().unlockElement('earth', 0);
    expect(result.success).toBe(true);
    const earth = useManaStore.getState().elements.earth;
    expect(earth).toBeDefined();
    expect(earth.unlocked).toBe(true);
    expect(earth.current).toBe(0);
    expect(earth.max).toBe(10);
    expect(earth.baseMax).toBe(10);
  });

  it('unlockElement preserves earth state fields', () => {
    useManaStore.setState((s) => ({
      elements: {
        ...s.elements,
        earth: { ...s.elements.earth, current: 5, max: 20, baseMax: 20 },
      },
    }));

    const result = useManaStore.getState().unlockElement('earth', 0);
    expect(result.success).toBe(true);

    const earth = useManaStore.getState().elements.earth;
    expect(earth.unlocked).toBe(true);
    expect(earth.current).toBe(5);
    expect(earth.max).toBe(20);
    expect(earth.baseMax).toBe(20);
  });

  it('unlock all elements via unlockElement preserves all fields', () => {
    const elements = useManaStore.getState().elements;
    for (const id of Object.keys(elements)) {
      if (!elements[id].unlocked) {
        useManaStore.getState().unlockElement(id, 0);
      }
    }

    const updatedElements = useManaStore.getState().elements;
    const earth = updatedElements.earth;

    expect(earth).toBeDefined();
    expect(earth.unlocked).toBe(true);
    expect(earth.current).toBe(0);
    expect(earth.max).toBe(10);
    expect(earth.baseMax).toBe(10);
  });

  it('all 22 elements are unlocked after unlock all', () => {
    const elements = useManaStore.getState().elements;
    for (const id of Object.keys(elements)) {
      if (!elements[id].unlocked) {
        useManaStore.getState().unlockElement(id, 0);
      }
    }

    const updatedElements = useManaStore.getState().elements;
    const unlockedCount = Object.values(updatedElements).filter(e => e.unlocked).length;
    expect(unlockedCount).toBe(Object.keys(ELEMENTS).length);
  });

  it('earth shows in unlocked elements list after unlock all', () => {
    const elements = useManaStore.getState().elements;
    for (const id of Object.keys(elements)) {
      if (!elements[id].unlocked) {
        useManaStore.getState().unlockElement(id, 0);
      }
    }

    const updatedElements = useManaStore.getState().elements;
    const unlockedIds = Object.entries(updatedElements)
      .filter(([, state]) => state.unlocked)
      .map(([id]) => id);

    expect(unlockedIds).toContain('earth');
  });

  it('earth element has valid state after unlock (not missing fields)', () => {
    useManaStore.getState().unlockElement('earth', 0);
    const earth = useManaStore.getState().elements.earth;

    expect(earth.current).toBeDefined();
    expect(earth.max).toBeDefined();
    expect(earth.baseMax).toBeDefined();
    expect(earth.unlocked).toBe(true);
  });

  it('unlockElement with cost 0 does not deduct raw mana', () => {
    const before = useManaStore.getState().rawMana;
    useManaStore.getState().unlockElement('earth', 0);
    const after = useManaStore.getState().rawMana;
    expect(after).toBe(before);
  });

  it('unlockElement twice for earth is a no-op', () => {
    const result1 = useManaStore.getState().unlockElement('earth', 0);
    expect(result1.success).toBe(true);

    const result2 = useManaStore.getState().unlockElement('earth', 0);
    expect(result2.success).toBe(false);

    const earth = useManaStore.getState().elements.earth;
    expect(earth.unlocked).toBe(true);
  });

  it('unlockElement on missing element creates valid state', () => {
    // Simulate an element that doesn't exist in the store
    useManaStore.setState((s) => {
      const { frost, ...rest } = s.elements;
      return { elements: rest as Record<string, { current: number; max: number; baseMax: number; unlocked: boolean }> };
    });

    expect(useManaStore.getState().elements.frost).toBeUndefined();

    const result = useManaStore.getState().unlockElement('frost', 0);
    expect(result.success).toBe(true);

    const frost = useManaStore.getState().elements.frost;
    expect(frost).toBeDefined();
    expect(frost.unlocked).toBe(true);
    expect(frost.current).toBe(0);
    expect(frost.max).toBe(10);
    expect(frost.baseMax).toBe(10);
  });
});

describe('Migration: old save missing elements', () => {
  beforeEach(() => {
    resetManaStore();
  });

  it('old save with missing elements can still unlock earth', () => {
    setOldSaveMissingElements();

    const elements = useManaStore.getState().elements;
    expect(elements.earth).toBeDefined();
    expect(elements.earth.unlocked).toBe(false);
    expect(elements.earth.current).toBe(5); // Preserved from old save

    const result = useManaStore.getState().unlockElement('earth', 0);
    expect(result.success).toBe(true);

    const earth = useManaStore.getState().elements.earth;
    expect(earth.unlocked).toBe(true);
    expect(earth.current).toBe(5); // Preserved
    expect(earth.max).toBe(10);
    expect(earth.baseMax).toBe(10);
  });

  it('old save earth current mana is preserved after unlock', () => {
    setOldSaveMissingElements();

    useManaStore.getState().unlockElement('earth', 0);
    const earth = useManaStore.getState().elements.earth;

    expect(earth.current).toBe(5); // From old save
    expect(earth.unlocked).toBe(true);
  });

  it('unlock all on old save unlocks earth and preserves its state', () => {
    setOldSaveMissingElements();

    const elements = useManaStore.getState().elements;
    for (const id of Object.keys(elements)) {
      if (!elements[id].unlocked) {
        useManaStore.getState().unlockElement(id, 0);
      }
    }

    const earth = useManaStore.getState().elements.earth;
    expect(earth.unlocked).toBe(true);
    expect(earth.current).toBe(5); // Preserved from old save
  });

  it('count of unlocked elements matches ELEMENTS count after unlock all on old save', () => {
    setOldSaveMissingElements();

    const elements = useManaStore.getState().elements;
    for (const id of Object.keys(elements)) {
      if (!elements[id].unlocked) {
        useManaStore.getState().unlockElement(id, 0);
      }
    }

    // Only the 8 elements from the old save are present and unlocked
    const updatedElements = useManaStore.getState().elements;
    const unlockedCount = Object.values(updatedElements).filter(e => e.unlocked).length;
    expect(unlockedCount).toBe(8); // Only old save elements
    expect(Object.keys(updatedElements).length).toBe(8);
  });
});
