import { describe, it, expect, vi, beforeEach } from "vitest";
import { invoke } from "@tauri-apps/api/core";
import type { MediaItem } from "$lib/api/types";
import { auth } from "$lib/stores/auth";

// Mock modules
vi.mock("@tauri-apps/api/core", () => ({
  invoke: vi.fn(),
}));

vi.mock("@tauri-apps/plugin-os", () => ({
  platform: vi.fn(() => "linux"),
}));

vi.mock("$lib/api/client", () => ({
  default: class {},
}));

vi.mock("$lib/stores/auth", () => ({
  auth: {
    getRepository: vi.fn(),
  },
}));

describe("TrackList Logic Tests", () => {
  const mockRepository = {
    getAudioStreamUrl: vi.fn(),
    getImageUrl: vi.fn(),
  };

  const mockTracks: MediaItem[] = [
    {
      id: "track-1",
      name: "Song 1",
      type: "Audio",
      serverId: "server-1",
      artists: ["Artist 1"],
      albumName: "Album 1",
      albumId: "album-1",
      runTimeTicks: 1800000000,
      primaryImageTag: "tag1",
      indexNumber: 1,
    },
    {
      id: "track-2",
      name: "Song 2",
      type: "Audio",
      serverId: "server-1",
      artists: ["Artist 2"],
      albumName: "Album 2",
      albumId: "album-2",
      runTimeTicks: 2400000000,
      primaryImageTag: "tag2",
      indexNumber: 2,
    },
  ];

  beforeEach(() => {
    vi.clearAllMocks();
    (auth.getRepository as any).mockReturnValue(mockRepository);
    mockRepository.getAudioStreamUrl.mockResolvedValue("http://stream.url/track");
    mockRepository.getImageUrl.mockReturnValue("http://image.url/artwork");
  });

  describe("Queue Building Logic", () => {
    it("should build correct queue structure from tracks", async () => {
      (invoke as any).mockResolvedValue(undefined);

      // Simulate the queue building logic from defaultHandleTrackClick
      const repo = auth.getRepository();
      const queueItems = await Promise.all(
        mockTracks.map(async (t) => ({
          id: t.id,
          title: t.name,
          artist: t.artists?.join(", "),
          album: t.albumName,
          duration: t.runTimeTicks ? t.runTimeTicks / 10000000 : undefined,
          artworkUrl: t.primaryImageTag
            ? repo.getImageUrl(t.albumId || t.id, "Primary", {
                maxWidth: 300,
                tag: t.primaryImageTag,
              })
            : undefined,
          mediaType: "Audio",
          streamUrl: await repo.getAudioStreamUrl(t.id),
          jellyfinItemId: t.id,
        }))
      );

      expect(queueItems).toHaveLength(2);
      expect(queueItems[0]).toMatchObject({
        id: "track-1",
        title: "Song 1",
        artist: "Artist 1",
        album: "Album 1",
        duration: 180,
        mediaType: "Audio",
      });
      expect(queueItems[0].streamUrl).toBe("http://stream.url/track");
      expect(queueItems[0].artworkUrl).toBe("http://image.url/artwork");
    });

    it("should call getAudioStreamUrl for each track", async () => {
      const repo = auth.getRepository();

      await Promise.all(
        mockTracks.map(async (t) => {
          const streamUrl = await repo.getAudioStreamUrl(t.id);
          return {
            id: t.id,
            streamUrl,
          };
        })
      );

      expect(mockRepository.getAudioStreamUrl).toHaveBeenCalledTimes(2);
      expect(mockRepository.getAudioStreamUrl).toHaveBeenCalledWith("track-1");
      expect(mockRepository.getAudioStreamUrl).toHaveBeenCalledWith("track-2");
    });

    it("should handle tracks without artwork", async () => {
      const trackWithoutArt: MediaItem = {
        ...mockTracks[0],
        primaryImageTag: undefined,
      };

      const repo = auth.getRepository();
      const artworkUrl = trackWithoutArt.primaryImageTag
        ? repo.getImageUrl(trackWithoutArt.albumId || trackWithoutArt.id, "Primary", {
            maxWidth: 300,
            tag: trackWithoutArt.primaryImageTag,
          })
        : undefined;

      expect(artworkUrl).toBeUndefined();
      expect(mockRepository.getImageUrl).not.toHaveBeenCalled();
    });

    it("should handle tracks without artist", async () => {
      const trackWithoutArtist: MediaItem = {
        ...mockTracks[0],
        artists: undefined,
      };

      const artistString = trackWithoutArtist.artists?.join(", ");
      expect(artistString).toBeUndefined();
    });

    it("should join multiple artists with comma", () => {
      const track: MediaItem = {
        ...mockTracks[0],
        artists: ["Artist 1", "Artist 2", "Artist 3"],
      };

      const artistString = track.artists?.join(", ");
      expect(artistString).toBe("Artist 1, Artist 2, Artist 3");
    });
  });

  describe("Duration Formatting", () => {
    function formatDuration(ticks?: number): string {
      if (!ticks) return "-";
      const seconds = Math.floor(ticks / 10000000);
      const mins = Math.floor(seconds / 60);
      const secs = seconds % 60;
      return `${mins}:${secs.toString().padStart(2, "0")}`;
    }

    it("should format duration correctly", () => {
      expect(formatDuration(1800000000)).toBe("3:00"); // 3 minutes
      expect(formatDuration(2400000000)).toBe("4:00"); // 4 minutes
      expect(formatDuration(3000000000)).toBe("5:00"); // 5 minutes
    });

    it("should handle seconds padding", () => {
      expect(formatDuration(650000000)).toBe("1:05"); // 1:05
      expect(formatDuration(6150000000)).toBe("10:15"); // 10:15
    });

    it("should return dash for undefined duration", () => {
      expect(formatDuration(undefined)).toBe("-");
      expect(formatDuration(0)).toBe("-");
    });
  });

  describe("Player Invocation", () => {
    it("should invoke player_play_queue with correct parameters", async () => {
      const invokeMock = (invoke as any).mockResolvedValue(undefined);

      const queueItems = [
        {
          id: "track-1",
          title: "Song 1",
          artist: "Artist 1",
          album: "Album 1",
          duration: 180,
          artworkUrl: "http://image.url/artwork",
          mediaType: "Audio",
          streamUrl: "http://stream.url/track",
          jellyfinItemId: "track-1",
        },
      ];

      await invoke("player_play_queue", {
        request: {
          items: queueItems,
          startIndex: 0,
          shuffle: false,
        },
      });

      expect(invokeMock).toHaveBeenCalledWith("player_play_queue", {
        request: {
          items: queueItems,
          startIndex: 0,
          shuffle: false,
        },
      });
    });

    it("should use correct startIndex for different positions", async () => {
      const invokeMock = (invoke as any).mockResolvedValue(undefined);
      const queueItems = mockTracks.map((t) => ({ id: t.id, title: t.name }));

      // Test clicking second track (index 1)
      await invoke("player_play_queue", {
        request: {
          items: queueItems,
          startIndex: 1,
          shuffle: false,
        },
      });

      const callArgs = invokeMock.mock.calls[0][1] as any;
      expect(callArgs.request.startIndex).toBe(1);
    });
  });

  describe("Error Handling", () => {
    it("should handle missing auth repository", () => {
      (auth.getRepository as any).mockReturnValue(null);

      const repo = auth.getRepository();
      expect(repo).toBeNull();

      // In the actual component, this would throw "Not authenticated"
      expect(() => {
        if (!repo) {
          throw new Error("Not authenticated");
        }
      }).toThrow("Not authenticated");

      // Restore for other tests
      (auth.getRepository as any).mockReturnValue(mockRepository);
    });

    it("should handle stream URL generation failure", async () => {
      mockRepository.getAudioStreamUrl.mockResolvedValue(null);

      const streamUrl = await mockRepository.getAudioStreamUrl("track-1");
      expect(streamUrl).toBeNull();

      // In the actual component, this would throw an error
      expect(() => {
        if (!streamUrl) {
          throw new Error("Failed to get stream URL for track");
        }
      }).toThrow("Failed to get stream URL");
    });

    it("should handle player invoke errors", async () => {
      const error = new Error("Network error");
      (invoke as any).mockRejectedValue(error);

      await expect(
        invoke("player_play_queue", {
          request: {
            items: [],
            startIndex: 0,
            shuffle: false,
          },
        })
      ).rejects.toThrow("Network error");
    });
  });

  describe("Callback vs Default Handler", () => {
    it("should use custom callback when provided", async () => {
      const customCallback = vi.fn();
      const track = mockTracks[0];
      const index = 0;

      // Simulate the unified handler logic
      const onTrackClick = customCallback;
      if (onTrackClick) {
        await onTrackClick(track, index);
      }

      expect(customCallback).toHaveBeenCalledWith(track, index);
      expect(customCallback).toHaveBeenCalledTimes(1);
    });

    it("should not invoke player when custom callback is provided", async () => {
      const customCallback = vi.fn();
      const invokeMock = (invoke as any).mockResolvedValue(undefined);

      // Simulate unified handler with custom callback
      const onTrackClick = customCallback;
      if (onTrackClick) {
        await onTrackClick(mockTracks[0], 0);
      } else {
        // This branch wouldn't execute
        await invoke("player_play_queue", {
          request: { items: [], startIndex: 0, shuffle: false },
        });
      }

      expect(customCallback).toHaveBeenCalled();
      expect(invokeMock).not.toHaveBeenCalled();
    });

    it("should invoke player when no custom callback", async () => {
      const invokeMock = (invoke as any).mockResolvedValue(undefined);

      // Simulate unified handler without custom callback - default handler runs
      await invoke("player_play_queue", {
        request: { items: [], startIndex: 0, shuffle: false },
      });

      expect(invokeMock).toHaveBeenCalled();
    });
  });
});
