import { describe, it, expect, vi, beforeEach } from "vitest";
import { spotifyIntegration } from "./index";

vi.mock("@/lib/api", () => ({
  API_BASE_URL: "",
  withAgentExecutionScope: (
    url: string,
    agentName?: string | null,
    executionScope?: string | null,
  ) => {
    const parsed = new URL(url, "https://frontend.example.test");
    if (agentName) {
      parsed.searchParams.set("agent_name", agentName);
    }
    if (executionScope) {
      parsed.searchParams.set("execution_scope", executionScope);
    }
    return `${parsed.pathname}${parsed.search}`;
  },
}));

// Mock fetch and window.open
global.fetch = vi.fn();
global.window.open = vi.fn();

// Mock localStorage
const localStorageMock = {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
};
Object.defineProperty(window, "localStorage", { value: localStorageMock });

describe("SpotifyIntegrationProvider", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    (global.fetch as any).mockReset();
    localStorageMock.getItem.mockReset();
    localStorageMock.setItem.mockReset();
    localStorageMock.removeItem.mockReset();
  });

  describe("getConfig", () => {
    it("should return correct integration configuration", () => {
      const config = spotifyIntegration.getConfig();

      expect(config.integration.id).toBe("spotify");
      expect(config.integration.name).toBe("Spotify");
      expect(config.integration.description).toBe(
        "Music streaming service integration",
      );
      expect(config.integration.category).toBe("entertainment");
      expect(config.integration.setup_type).toBe("oauth");
      expect(config.integration.status).toBe("available");
      expect(config.integration.connected).toBe(false);
    });

    it("should provide onAction handler", () => {
      const config = spotifyIntegration.getConfig();
      expect(config.onAction).toBeDefined();
      expect(typeof config.onAction).toBe("function");
    });

    it("should provide onDisconnect handler", () => {
      const config = spotifyIntegration.getConfig();
      expect(config.onDisconnect).toBeDefined();
      expect(typeof config.onDisconnect).toBe("function");
    });
  });

  describe("loadStatus", () => {
    it("should return connected status when configured", async () => {
      localStorageMock.getItem.mockReturnValue("true");

      const status = await spotifyIntegration.loadStatus();

      expect(status.status).toBe("connected");
      expect(status.connected).toBe(true);
    });

    it("should return available status when not configured", async () => {
      localStorageMock.getItem.mockReturnValue(null);
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ connected: false }),
      });

      const status = await spotifyIntegration.loadStatus();

      expect(status.status).toBe("available");
      expect(status.connected).toBe(false);
    });

    it("appends agent_name and execution_scope when checking scoped status", async () => {
      localStorageMock.getItem.mockReturnValue(null);
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ connected: false }),
      });

      await spotifyIntegration.loadStatus({
        agentName: "code",
        executionScope: "shared",
      });

      expect(localStorageMock.getItem).toHaveBeenCalledWith(
        "spotify_configured:code:shared",
      );
      expect(global.fetch).toHaveBeenCalledWith(
        "/api/integrations/spotify/status?agent_name=code&execution_scope=shared",
      );
    });

    it("does not reuse optimistic scoped cache entries across execution scopes", async () => {
      localStorageMock.getItem.mockImplementation((key: string) =>
        key === "spotify_configured:code:user" ? "true" : null,
      );
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ connected: false }),
      });

      const status = await spotifyIntegration.loadStatus({
        agentName: "code",
        executionScope: "shared",
      });

      expect(status.connected).toBe(false);
      expect(localStorageMock.getItem).toHaveBeenCalledWith(
        "spotify_configured:code:shared",
      );
      expect(global.fetch).toHaveBeenCalledWith(
        "/api/integrations/spotify/status?agent_name=code&execution_scope=shared",
      );
    });
  });

  describe("onAction (connect)", () => {
    it("should initiate OAuth flow", async () => {
      const mockAuthWindow = { closed: false };
      (global.window.open as any).mockReturnValue(mockAuthWindow);
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ auth_url: "https://spotify.com/auth" }),
      });

      const config = spotifyIntegration.getConfig();
      expect(config.onAction).toBeDefined();
      const connectPromise = config.onAction!(config.integration);

      // Simulate window closing after a short delay
      setTimeout(() => {
        mockAuthWindow.closed = true;
      }, 100);

      await connectPromise;

      expect(global.fetch).toHaveBeenCalledWith(
        expect.stringContaining("/api/integrations/spotify/connect"),
        expect.objectContaining({ method: "POST" }),
      );
      expect(global.window.open).toHaveBeenCalledWith(
        "https://spotify.com/auth",
        "_blank",
        "width=500,height=600",
      );
    });

    it("should handle connection errors", async () => {
      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        json: async () => ({ detail: "Connection failed" }),
      });

      const config = spotifyIntegration.getConfig();

      await expect(config.onAction!(config.integration)).rejects.toThrow(
        "Connection failed",
      );
    });

    it("appends agent_name and execution_scope for scoped OAuth connect", async () => {
      const mockAuthWindow = { closed: true };
      (global.window.open as any).mockReturnValue(mockAuthWindow);
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ auth_url: "https://spotify.com/auth" }),
      });

      const config = spotifyIntegration.getConfig({
        agentName: "code",
        executionScope: "shared",
      });
      await config.onAction!(config.integration);

      expect(global.fetch).toHaveBeenCalledWith(
        "/api/integrations/spotify/connect?agent_name=code&execution_scope=shared",
        expect.objectContaining({ method: "POST" }),
      );
    });
  });

  describe("onDisconnect", () => {
    it("should remove localStorage and call disconnect endpoint", async () => {
      (global.fetch as any).mockResolvedValueOnce({ ok: true });

      const config = spotifyIntegration.getConfig();
      await config.onDisconnect!("spotify");

      expect(localStorageMock.removeItem).toHaveBeenCalledWith(
        "spotify_configured:unscoped",
      );
      expect(global.fetch).toHaveBeenCalledWith(
        expect.stringContaining("/api/integrations/spotify/disconnect"),
        expect.objectContaining({ method: "POST" }),
      );
    });

    it("should handle disconnect errors gracefully", async () => {
      (global.fetch as any).mockRejectedValueOnce(new Error("Network error"));

      const config = spotifyIntegration.getConfig();

      // Should not throw, just log error
      await expect(config.onDisconnect!("spotify")).resolves.not.toThrow();
      expect(localStorageMock.removeItem).toHaveBeenCalledWith(
        "spotify_configured:unscoped",
      );
    });

    it("appends agent_name and execution_scope for scoped disconnect", async () => {
      (global.fetch as any).mockResolvedValueOnce({ ok: true });

      const config = spotifyIntegration.getConfig({
        agentName: "code",
        executionScope: "shared",
      });
      await config.onDisconnect!("spotify");

      expect(localStorageMock.removeItem).toHaveBeenCalledWith(
        "spotify_configured:code:shared",
      );
      expect(global.fetch).toHaveBeenCalledWith(
        "/api/integrations/spotify/disconnect?agent_name=code&execution_scope=shared",
        expect.objectContaining({ method: "POST" }),
      );
    });
  });
});
