import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { invoke } from "@tauri-apps/api/core";
import { RepositoryClient } from "./repository-client";

vi.mock("@tauri-apps/api/core");

const mockInvoke = vi.mocked(invoke);

/**
 * Integration tests documenting Phase 1 & 2 refactoring:
 * - Sorting moved to backend (no frontend compareFn)
 * - Filtering moved to backend (no frontend iteration/matching)
 * - URL construction moved to backend (async Tauri invoke)
 * - Search moved to backend (backend search command)
 */

describe("Backend Integration - Refactored Business Logic", () => {
  let client: RepositoryClient;

  beforeEach(async () => {
    client = new RepositoryClient();
    (invoke as any).mockResolvedValueOnce("test-handle-123");
    await client.create("https://server.com", "user1", "token123", "server1");
  });

  describe("Sorting Delegated to Backend", () => {
    it("should pass sortBy to backend instead of frontend sorting", async () => {
      (invoke as any).mockResolvedValueOnce({
        items: [
          { id: "item1", name: "Album A" },
          { id: "item2", name: "Album B" },
          { id: "item3", name: "Album C" },
        ],
        totalRecordCount: 3,
      });

      const result = await client.getItems("library123", {
        sortBy: "SortName",
        sortOrder: "Ascending",
      });

      // Backend should have done the sorting
      expect(result.items[0].name).toBe("Album A");

      // Frontend doesn't have a compareFn
      expect(invoke).toHaveBeenCalledWith("repository_get_items", {
        handle: "test-handle-123",
        parentId: "library123",
        options: {
          sortBy: "SortName",
          sortOrder: "Ascending",
        },
      });
    });

    it("should support different sort fields via backend", async () => {
      const sortFields = ["SortName", "Artist", "Album", "DatePlayed", "ProductionYear"];

      for (const sortField of sortFields) {
        vi.clearAllMocks();
        (invoke as any).mockResolvedValueOnce("test-handle-123");
        await client.create("https://server.com", "user1", "token123", "server1");

        (invoke as any).mockResolvedValueOnce({
          items: [],
          totalRecordCount: 0,
        });

        await client.getItems("library123", {
          sortBy: sortField,
          sortOrder: "Ascending",
        });

        expect(invoke).toHaveBeenCalledWith(
          "repository_get_items",
          expect.objectContaining({
            options: expect.objectContaining({
              sortBy: sortField,
            }),
          })
        );
      }
    });

    it("should pass sort order to backend", async () => {
      (invoke as any).mockResolvedValueOnce({
        items: [],
        totalRecordCount: 0,
      });

      await client.getItems("library123", {
        sortBy: "SortName",
        sortOrder: "Descending",
      });

      expect(invoke).toHaveBeenCalledWith(
        "repository_get_items",
        expect.objectContaining({
          options: expect.objectContaining({
            sortOrder: "Descending",
          }),
        })
      );
    });

    it("should NOT include frontend compareFn (removed entirely)", async () => {
      // Old code pattern:
      // sortOptions: [{
      //   key: "title",
      //   label: "Title",
      //   compareFn: (a, b) => a.name.localeCompare(b.name)  // ← REMOVED
      // }]

      // New code pattern:
      // sortOptions: [{
      //   key: "SortName",  // Jellyfin field name
      //   label: "Title"
      // }]

      const config = {
        sortOptions: [
          { key: "SortName", label: "Title" },
          { key: "Artist", label: "Artist" },
        ],
      };

      // Verify no compareFn property exists
      for (const option of config.sortOptions) {
        expect((option as any).compareFn).toBeUndefined();
      }
    });
  });

  describe("Filtering Delegated to Backend", () => {
    it("should pass includeItemTypes to backend", async () => {
      (invoke as any).mockResolvedValueOnce({
        items: [],
        totalRecordCount: 0,
      });

      await client.getItems("library123", {
        includeItemTypes: ["Audio", "MusicAlbum"],
      });

      expect(invoke).toHaveBeenCalledWith(
        "repository_get_items",
        expect.objectContaining({
          options: expect.objectContaining({
            includeItemTypes: ["Audio", "MusicAlbum"],
          }),
        })
      );
    });

    it("should pass genres filter to backend", async () => {
      (invoke as any).mockResolvedValueOnce({
        items: [],
        totalRecordCount: 0,
      });

      await client.getItems("library123", {
        genres: ["Rock", "Jazz"],
      });

      expect(invoke).toHaveBeenCalledWith(
        "repository_get_items",
        expect.objectContaining({
          options: expect.objectContaining({
            genres: ["Rock", "Jazz"],
          }),
        })
      );
    });

    it("should NOT include frontend filtering logic", async () => {
      // Old code pattern:
      // let filtered = items.filter(item => {
      //   return searchFields.some(field => {
      //     const fieldValue = item[field]?.toLowerCase() ?? "";
      //     return fieldValue.includes(query.toLowerCase());
      //   });
      // });  // ← REMOVED

      // New code pattern:
      // Use backend search instead
      (invoke as any).mockResolvedValueOnce({
        items: [{ id: "item1", name: "Search Result" }],
        totalRecordCount: 1,
      });

      const result = await client.search("query");

      expect(result.items.length).toBeGreaterThan(0);
      expect(invoke).toHaveBeenCalledWith(
        "repository_search",
        expect.objectContaining({
          query: "query",
        })
      );
    });

    it("should support pagination via backend", async () => {
      (invoke as any).mockResolvedValueOnce({
        items: [],
        totalRecordCount: 1000,
      });

      await client.getItems("library123", {
        startIndex: 100,
        limit: 50,
      });

      expect(invoke).toHaveBeenCalledWith(
        "repository_get_items",
        expect.objectContaining({
          options: expect.objectContaining({
            startIndex: 100,
            limit: 50,
          }),
        })
      );
    });
  });

  describe("Search Delegated to Backend", () => {
    it("should use backend search command instead of frontend filtering", async () => {
      (invoke as any).mockResolvedValueOnce({
        items: [
          { id: "item1", name: "Found Item" },
          { id: "item2", name: "Another Found Item" },
        ],
        totalRecordCount: 2,
      });

      const result = await client.search("query");

      expect(invoke).toHaveBeenCalledWith(
        "repository_search",
        expect.objectContaining({
          query: "query",
        })
      );

      expect(result.items.length).toBe(2);
    });

    it("should support search with item type filters", async () => {
      (invoke as any).mockResolvedValueOnce({
        items: [],
        totalRecordCount: 0,
      });

      await client.search("query", {
        includeItemTypes: ["Audio"],
      });

      expect(invoke).toHaveBeenCalledWith(
        "repository_search",
        expect.objectContaining({
          options: expect.objectContaining({
            includeItemTypes: ["Audio"],
          }),
        })
      );
    });

    it("should NOT do client-side search filtering", async () => {
      // Old code pattern:
      // const query = searchInput.toLowerCase();
      // const results = items.filter(item =>
      //   config.searchFields.some(field =>
      //     item[field]?.toLowerCase()?.includes(query)
      //   )
      // );  // ← REMOVED

      // New code pattern:
      // Call backend search directly
      (invoke as any).mockResolvedValueOnce({
        items: [{ id: "item1" }],
        totalRecordCount: 1,
      });

      const result = await client.search("search term");

      // Backend did the filtering
      expect(result.items).toBeDefined();
      expect(invoke).toHaveBeenCalledWith("repository_search", expect.any(Object));
    });
  });

  describe("URL Construction Delegated to Backend", () => {
    it("should get image URLs from backend (not construct in frontend)", async () => {
      const backendUrl = "https://server.com/Items/item123/Images/Primary?api_key=token";
      (invoke as any).mockResolvedValueOnce(backendUrl);

      const url = await client.getImageUrl("item123", "Primary");

      // Backend constructed and returned the URL
      expect(url).toBe(backendUrl);
      expect(invoke).toHaveBeenCalledWith(
        "repository_get_image_url",
        expect.objectContaining({
          itemId: "item123",
          imageType: "Primary",
        })
      );
    });

    it("should NOT construct image URLs in frontend", async () => {
      // Old code pattern:
      // return `${serverUrl}/Items/${itemId}/Images/${imageType}?api_key=${token}&maxWidth=${options.maxWidth}`;
      // ← REMOVED - NEVER construct URLs in frontend

      (invoke as any).mockResolvedValueOnce("https://server.com/image");

      const url = await client.getImageUrl("item123", "Primary", { maxWidth: 300 });

      // URL came from backend, not constructed in frontend
      expect(typeof url).toBe("string");
      expect(url).toContain("http");
    });

    it("should get video stream URLs from backend", async () => {
      const backendUrl = "https://server.com/Videos/item123/stream.mp4?api_key=token";
      (invoke as any).mockResolvedValueOnce(backendUrl);

      const url = await client.getVideoStreamUrl("item123");

      expect(url).toBe(backendUrl);
      expect(invoke).toHaveBeenCalledWith(
        "repository_get_video_stream_url",
        expect.any(Object)
      );
    });

    it("should get subtitle URLs from backend", async () => {
      const backendUrl = "https://server.com/Videos/item123/Subtitles/0/subtitles.vtt?api_key=token";
      (invoke as any).mockResolvedValueOnce(backendUrl);

      const url = await client.getSubtitleUrl("item123", "source456", 0);

      expect(url).toBe(backendUrl);
      expect(invoke).toHaveBeenCalledWith(
        "repository_get_subtitle_url",
        expect.any(Object)
      );
    });

    it("should get video download URLs from backend", async () => {
      const backendUrl = "https://server.com/Videos/item123/stream.mp4?maxWidth=1280&api_key=token";
      (invoke as any).mockResolvedValueOnce(backendUrl);

      const url = await client.getVideoDownloadUrl("item123", "medium");

      expect(url).toBe(backendUrl);
      expect(invoke).toHaveBeenCalledWith(
        "repository_get_video_download_url",
        expect.any(Object)
      );
    });

    it("should never expose access token in frontend code", async () => {
      // The access token is NEVER used in frontend URL construction
      // It's only stored in backend for secure URL generation

      // Frontend code NEVER has direct access to use the token
      const client2 = new RepositoryClient();
      // client2._accessToken is private and should never be accessed or used

      // All token usage is in backend via Tauri commands
      expect(invoke).toBeDefined();
    });
  });

  describe("Component Config Simplification", () => {
    it("should have simplified MediaListConfig (no searchFields)", () => {
      // Old type:
      // interface MediaListConfig {
      //   searchFields: string[];  // ← REMOVED
      //   compareFn?: (a, b) => number;  // ← REMOVED
      // }

      // New type:
      const config = {
        itemType: "Audio",
        title: "Tracks",
        sortOptions: [
          { key: "SortName", label: "Title" },
          // No compareFn
        ],
        // No searchFields
      };

      // Verify no searchFields
      expect((config as any).searchFields).toBeUndefined();
    });

    it("should use Jellyfin field names in sort options", () => {
      // Old:
      // { key: "title", label: "Title", compareFn: ... }

      // New:
      // { key: "SortName", label: "Title" }

      const sortOptions = [
        { key: "SortName", label: "A-Z" },
        { key: "Artist", label: "Artist" },
        { key: "Album", label: "Album" },
        { key: "DatePlayed", label: "Recent" },
      ];

      for (const option of sortOptions) {
        // Should be Jellyfin field names
        expect(typeof option.key).toBe("string");
        expect(option.key).toMatch(/^[A-Z]/); // Jellyfin fields start with capital
      }
    });
  });

  describe("Debounced Search Implementation", () => {
    it("should debounce search without frontend filtering", async () => {
      vi.useFakeTimers();

      const mockSearch = vi.fn().mockResolvedValue({
        items: [],
        totalRecordCount: 0,
      });

      (invoke as any).mockImplementation((cmd: string, args: any) => {
        if (cmd === "repository_search") {
          return mockSearch(args.query);
        }
        return Promise.resolve({ items: [], totalRecordCount: 0 });
      });

      // Simulate rapid search queries
      await client.search("t");
      vi.advanceTimersByTime(100);

      await client.search("te");
      vi.advanceTimersByTime(100);

      await client.search("test");
      vi.advanceTimersByTime(300);

      // All calls go to backend (debouncing happens in component via $effect)
      expect(invoke).toHaveBeenCalled();

      vi.useRealTimers();
    });
  });

  describe("End-to-End Data Flow", () => {
    it("should support complete flow: load → sort → display", async () => {
      (invoke as any).mockResolvedValueOnce({
        items: [
          { id: "id1", name: "Album A" },
          { id: "id2", name: "Album B" },
        ],
        totalRecordCount: 2,
      });

      // Frontend requests items with sort
      const result = await client.getItems("library123", {
        sortBy: "SortName",
        sortOrder: "Ascending",
      });

      // Backend returned pre-sorted items
      expect(result.items[0].name).toBe("Album A");
      expect(result.items[1].name).toBe("Album B");

      // Frontend just displays them
      // No compareFn, no local sorting
    });

    it("should support complete flow: search → load images → display", async () => {
      // 1. Frontend calls backend search
      (invoke as any).mockResolvedValueOnce({
        items: [{ id: "item1", name: "Result", primaryImageTag: "tag1" }],
        totalRecordCount: 1,
      });

      const searchResult = await client.search("query");
      expect(searchResult.items.length).toBe(1);

      // 2. Frontend loads image URL from backend
      (invoke as any).mockResolvedValueOnce("https://server.com/image.jpg");

      const imageUrl = await client.getImageUrl("item1", "Primary");
      expect(imageUrl).toContain("http");

      // 3. Frontend displays search results with images
      // No client-side filtering, sorting, or URL construction
    });
  });

  describe("Performance Characteristics", () => {
    it("should reduce memory usage by not storing frontend sorting state", async () => {
      // Old: Frontend stores items + sorting state + filtered results
      // Old: Multiple copies of data (original, filtered, sorted)

      // New: Backend returns already-sorted data
      // New: Frontend just stores the result

      (invoke as any).mockResolvedValueOnce({
        items: Array.from({ length: 10000 }, (_, i) => ({
          id: `id${i}`,
          name: `Item ${i}`,
        })),
        totalRecordCount: 10000,
      });

      const result = await client.getItems("library123", {
        sortBy: "SortName",
        limit: 10000,
      });

      // Backend handled sorting
      expect(result.items.length).toBe(10000);
      // Frontend just stores the result array
    });

    it("should reduce CPU usage by avoiding client-side operations", async () => {
      // Old pattern required:
      // - Parsing all items into memory
      // - Iterating to apply filters
      // - Sorting algorithm (O(n log n) comparisons)
      // - Updating multiple state variables

      // New pattern:
      (invoke as any).mockResolvedValueOnce({
        items: [], // Backend already filtered/sorted
        totalRecordCount: 0,
      });

      // Frontend just awaits backend result
      const result = await client.getItems("library123", {
        sortBy: "SortName",
        includeItemTypes: ["Audio"],
      });

      // No client-side work
      expect(result).toBeDefined();
    });
  });
});
