/**
 * Playback Reporting service tests
 *
 * TRACES: UR-005, UR-019, UR-025 | DR-028, DR-047
 */

import { describe, it, expect, beforeEach, vi } from "vitest";
import {
  reportPlaybackStart,
  reportPlaybackProgress,
  reportPlaybackStopped,
  markAsPlayed,
} from "./playbackReporting";

vi.mock("@tauri-apps/api/core", () => ({
  invoke: vi.fn(async (command: string) => {
    if (command.startsWith("storage_")) {
      return undefined;
    }
    return null;
  }),
}));

vi.mock("$lib/stores/auth", () => ({
  auth: {
    getUserId: vi.fn(() => "user-123"),
    getRepository: vi.fn(() => ({
      reportPlaybackStopped: vi.fn(async () => undefined),
      getItem: vi.fn(async (id: string) => ({
        id,
        name: "Test Item",
        durationMs: 10000,
      })),
    })),
  },
}));

describe("playback reporting service", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("reportPlaybackStart", () => {
    it("should accept itemId and positionSeconds", async () => {
      await expect(reportPlaybackStart("item-123", 0)).resolves.toBeUndefined();
    });

    it("should accept optional contextType and contextId", async () => {
      await expect(
        reportPlaybackStart("item-123", 0, "container", "container-456")
      ).resolves.toBeUndefined();
    });

    it("should convert seconds to ticks", async () => {
      const { invoke } = await import("@tauri-apps/api/core");
      const invokeSpy = vi.mocked(invoke);

      await reportPlaybackStart("item-123", 60);

      const call = invokeSpy.mock.calls.find(
        (c) => c[0] === "storage_update_playback_context"
      );
      expect(call).toBeDefined();
      expect(call![1]).toHaveProperty("positionMs", 60000); // 60 seconds
    });

    it("should use single context by default", async () => {
      const { invoke } = await import("@tauri-apps/api/core");
      const invokeSpy = vi.mocked(invoke);

      await reportPlaybackStart("item-123", 30);

      const call = invokeSpy.mock.calls.find(
        (c) => c[0] === "storage_update_playback_context"
      );
      expect(call![1]).toHaveProperty("contextType", "single");
      expect(call![1]).toHaveProperty("contextId", null);
    });

    it("should include userId in command", async () => {
      const { invoke } = await import("@tauri-apps/api/core");
      const invokeSpy = vi.mocked(invoke);

      await reportPlaybackStart("item-123", 0);

      const call = invokeSpy.mock.calls.find(
        (c) => c[0] === "storage_update_playback_context"
      );
      expect(call![1]).toHaveProperty("userId", "user-123");
    });
  });

  describe("reportPlaybackProgress", () => {
    it("should accept itemId and positionSeconds", async () => {
      await expect(reportPlaybackProgress("item-123", 30)).resolves.toBeUndefined();
    });

    it("should accept optional isPaused parameter", async () => {
      await expect(reportPlaybackProgress("item-123", 30, true)).resolves.toBeUndefined();
    });

    it("should update local progress only", async () => {
      const { invoke } = await import("@tauri-apps/api/core");
      const invokeSpy = vi.mocked(invoke);

      await reportPlaybackProgress("item-123", 30);

      const call = invokeSpy.mock.calls.find(
        (c) => c[0] === "storage_update_playback_progress"
      );
      expect(call).toBeDefined();
      expect(call![1]).toHaveProperty("itemId", "item-123");
    });

    it("should convert seconds to ticks", async () => {
      const { invoke } = await import("@tauri-apps/api/core");
      const invokeSpy = vi.mocked(invoke);

      await reportPlaybackProgress("item-123", 45);

      const call = invokeSpy.mock.calls.find(
        (c) => c[0] === "storage_update_playback_progress"
      );
      expect(call![1]).toHaveProperty("positionMs", 45000); // 45 seconds
    });
  });

  describe("reportPlaybackStopped", () => {
    it("should accept itemId and positionSeconds", async () => {
      await expect(reportPlaybackStopped("item-123", 120)).resolves.toBeUndefined();
    });

    it("should update local progress", async () => {
      const { invoke } = await import("@tauri-apps/api/core");
      const invokeSpy = vi.mocked(invoke);

      await reportPlaybackStopped("item-123", 120);

      const call = invokeSpy.mock.calls.find(
        (c) => c[0] === "storage_update_playback_progress"
      );
      expect(call).toBeDefined();
    });

    it("should report to server via repository", async () => {
      const { auth } = await import("$lib/stores/auth");
      const authModule = vi.mocked(auth);
      const mockRepo = {
        reportPlaybackStopped: vi.fn(async () => undefined),
      };
      authModule.getRepository = vi.fn(() => mockRepo as any);

      await reportPlaybackStopped("item-123", 120);

      expect(mockRepo.reportPlaybackStopped).toHaveBeenCalled();
    });

    it("should convert seconds to milliseconds for server report", async () => {
      const { auth } = await import("$lib/stores/auth");
      const authModule = vi.mocked(auth);
      const mockRepo = {
        reportPlaybackStopped: vi.fn(async () => undefined),
      };
      authModule.getRepository = vi.fn(() => mockRepo as any);

      await reportPlaybackStopped("item-123", 90);

      expect(mockRepo.reportPlaybackStopped).toHaveBeenCalledWith(
        "item-123",
        90000 // 90 seconds in ms
      );
    });

    it("should not report to server if positionSeconds is 0", async () => {
      const { auth } = await import("$lib/stores/auth");
      const authModule = vi.mocked(auth);
      const mockRepo = {
        reportPlaybackStopped: vi.fn(async () => undefined),
      };
      authModule.getRepository = vi.fn(() => mockRepo as any);

      await reportPlaybackStopped("item-123", 0);

      expect(mockRepo.reportPlaybackStopped).not.toHaveBeenCalled();
    });
  });

  describe("markAsPlayed", () => {
    it("should mark item as played", async () => {
      const { invoke } = await import("@tauri-apps/api/core");
      const invokeSpy = vi.mocked(invoke);

      await markAsPlayed("item-123");

      const call = invokeSpy.mock.calls.find(
        (c) => c[0] === "storage_mark_played"
      );
      expect(call).toBeDefined();
      expect(call![1]).toHaveProperty("itemId", "item-123");
    });

    it("should report to server with full duration", async () => {
      const { auth } = await import("$lib/stores/auth");
      const authModule = vi.mocked(auth);
      const mockRepo = {
        reportPlaybackStopped: vi.fn(async () => undefined),
        getItem: vi.fn(async () => ({
          id: "item-123",
          name: "Item",
          durationMs: 10000,
        })),
      };
      authModule.getRepository = vi.fn(() => mockRepo as any);

      await markAsPlayed("item-123");

      expect(mockRepo.getItem).toHaveBeenCalledWith("item-123");
      expect(mockRepo.reportPlaybackStopped).toHaveBeenCalledWith(
        "item-123",
        10000
      );
    });

    it("should handle items without durationMs", async () => {
      const { auth } = await import("$lib/stores/auth");
      const authModule = vi.mocked(auth);
      const mockRepo = {
        reportPlaybackStopped: vi.fn(async () => undefined),
        getItem: vi.fn(async () => ({
          id: "item-123",
          name: "Item",
          durationMs: null,
        })),
      };
      authModule.getRepository = vi.fn(() => mockRepo as any);

      await markAsPlayed("item-123");

      expect(mockRepo.reportPlaybackStopped).not.toHaveBeenCalled();
    });
  });
});
