import { describe, it, expect, vi } from "vitest";
import { get } from "svelte/store";
import { setMockInvokeResult, emitMockEvent } from "../../../vitest.setup";
import {
  achievementsStore,
  unlockedAchievements,
  lockedAchievements,
  achievementsByRarity,
  achievementProgress,
  initAchievementsListener,
} from "./achievements";
import type { AchievementUnlockedEvent } from "$lib/types/achievements";
import { playAchievementSound } from "$lib/sounds/achievement";

vi.mock("$lib/sounds/achievement", () => ({
  playAchievementSound: vi.fn(),
}));

// Helper to build a minimal unlock event
function makeEvent(id: AchievementUnlockedEvent["achievement"]["id"]): AchievementUnlockedEvent {
  return {
    achievement: {
      id,
      name: "Test",
      description: "Test achievement",
      icon: "🏆",
      unlocked_at: null,
    },
  };
}

describe("achievementsStore initial state", () => {
  it("all achievements start as locked", () => {
    const state = get(achievementsStore);
    expect(state.achievements["FirstSteps"].unlocked).toBe(false);
    expect(state.achievements["GrowingStrong"].unlocked).toBe(false);
  });

  it("totalUnlocked starts at 0", () => {
    expect(get(achievementsStore).totalUnlocked).toBe(0);
  });

  it("lastUnlocked starts as null", () => {
    expect(get(achievementsStore).lastUnlocked).toBeNull();
  });
});

describe("derived stores initial state", () => {
  it("unlockedAchievements is initially empty", () => {
    expect(get(unlockedAchievements)).toEqual([]);
  });

  it("lockedAchievements contains all achievements initially", () => {
    const locked = get(lockedAchievements);
    const total = Object.keys(get(achievementsStore).achievements).length;
    expect(locked.length).toBe(total);
  });

  it("achievementsByRarity groups achievements into rarity buckets", () => {
    const byRarity = get(achievementsByRarity);
    expect(byRarity.common).toBeInstanceOf(Array);
    expect(byRarity.rare).toBeInstanceOf(Array);
    expect(byRarity.epic).toBeInstanceOf(Array);
    expect(byRarity.legendary).toBeInstanceOf(Array);
    expect(byRarity.common.length).toBeGreaterThan(0);
  });

  it("achievementProgress shows zero unlocked initially", () => {
    const progress = get(achievementProgress);
    expect(progress.unlocked).toBe(0);
    expect(progress.total).toBeGreaterThan(0);
    expect(progress.percentage).toBe(0);
  });
});

describe("achievementsStore.unlockAchievement", () => {
  it("marks the achievement as unlocked and updates totalUnlocked", () => {
    achievementsStore.unlockAchievement(makeEvent("GrowingStrong"));
    const state = get(achievementsStore);
    expect(state.achievements["GrowingStrong"].unlocked).toBe(true);
    expect(state.totalUnlocked).toBe(1);
    expect(state.lastUnlocked?.id).toBe("GrowingStrong");
  });

  it("sets unlockedAt from the event's unlocked_at timestamp", () => {
    achievementsStore.unlockAchievement({
      achievement: {
        id: "BlossomingCoder",
        name: "Blossoming Coder",
        description: "100k tokens",
        icon: "🌸",
        unlocked_at: "2026-01-15T12:00:00.000Z",
      },
    });
    const state = get(achievementsStore);
    expect(state.achievements["BlossomingCoder"].unlockedAt).toBeInstanceOf(Date);
  });

  it("does nothing when the achievement is already unlocked", () => {
    achievementsStore.unlockAchievement(makeEvent("TokenMaster"));
    const firstTotal = get(achievementsStore).totalUnlocked;
    achievementsStore.unlockAchievement(makeEvent("TokenMaster"));
    expect(get(achievementsStore).totalUnlocked).toBe(firstTotal);
  });

  it("calls playAchievementSound when playSound is true (default)", () => {
    achievementsStore.unlockAchievement(makeEvent("TokenBillionaire"));
    expect(playAchievementSound).toHaveBeenCalled();
  });

  it("does not call playAchievementSound when playSound is false", () => {
    achievementsStore.unlockAchievement(makeEvent("TokenTreasure"), false);
    expect(playAchievementSound).not.toHaveBeenCalled();
  });

  it("logs an error when playAchievementSound throws", () => {
    vi.mocked(playAchievementSound).mockImplementationOnce(() => {
      throw new Error("Sound failed");
    });
    const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
    achievementsStore.unlockAchievement(makeEvent("HelloWorld"));
    expect(consoleSpy).toHaveBeenCalledWith("Failed to play achievement sound:", expect.any(Error));
    consoleSpy.mockRestore();
  });
});

describe("derived stores after unlocks", () => {
  it("unlockedAchievements includes previously unlocked achievements", () => {
    const unlocked = get(unlockedAchievements);
    expect(unlocked.some((a) => a.id === "GrowingStrong")).toBe(true);
  });

  it("lockedAchievements excludes previously unlocked achievements", () => {
    const locked = get(lockedAchievements);
    expect(locked.some((a) => a.id === "GrowingStrong")).toBe(false);
  });

  it("achievementProgress reflects the current unlocked count", () => {
    const progress = get(achievementProgress);
    expect(progress.unlocked).toBeGreaterThan(0);
    expect(progress.percentage).toBeGreaterThan(0);
  });
});

describe("achievementsStore.updateProgress", () => {
  it("updates the progress value for an achievement", () => {
    achievementsStore.updateProgress("FirstMessage", 50);
    expect(get(achievementsStore).achievements["FirstMessage"].progress).toBe(50);
  });
});

describe("achievementsStore.reset", () => {
  it("resets totalUnlocked to 0 and lastUnlocked to null", () => {
    achievementsStore.reset();
    const state = get(achievementsStore);
    expect(state.totalUnlocked).toBe(0);
    expect(state.lastUnlocked).toBeNull();
  });
});

describe("initAchievementsListener", () => {
  it("unlocks an achievement when the achievement:unlocked event fires", async () => {
    await initAchievementsListener();
    emitMockEvent("achievement:unlocked", makeEvent("FirstSteps"));
    expect(get(achievementsStore).achievements["FirstSteps"].unlocked).toBe(true);
  });

  it("loads saved achievements from the backend without playing sounds", async () => {
    setMockInvokeResult("load_saved_achievements", [makeEvent("ConversationStarter")]);
    await initAchievementsListener();
    expect(get(achievementsStore).achievements["ConversationStarter"].unlocked).toBe(true);
    expect(playAchievementSound).not.toHaveBeenCalled();
  });

  it("logs an error when loading saved achievements fails", async () => {
    setMockInvokeResult("load_saved_achievements", new Error("Storage unavailable"));
    const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
    await initAchievementsListener();
    expect(consoleSpy).toHaveBeenCalledWith(
      "Failed to load saved achievements:",
      expect.any(Error)
    );
    consoleSpy.mockRestore();
  });
});
