// @vitest-environment jsdom

import { act, cleanup, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ScreenPlayer } from "@/components/screen-player";
import { getTvImpressionQueueStorageKey, getTvMediaCacheName, type ScreenManifest } from "@/lib/tvs-runtime";

type MockCache = {
  delete: ReturnType<typeof vi.fn>;
  keys: ReturnType<typeof vi.fn>;
  match: ReturnType<typeof vi.fn>;
  put: ReturnType<typeof vi.fn>;
};

const initialManifest: ScreenManifest = {
  version: "ballbox-db-2026-03-20T18:00:00.000Z",
  screenId: "screen_001",
  generatedAt: "2026-03-20T18:00:00.000Z",
  creatives: [
    {
      id: "creative_001",
      campaignId: "campaign_001",
      campaignName: "Babolat",
      url: "/videos/hero.mp4",
      duration: 15,
    },
  ],
};

function createJsonResponse(body: unknown, init?: ResponseInit) {
  return {
    ok: (init?.status ?? 200) >= 200 && (init?.status ?? 200) < 300,
    status: init?.status ?? 200,
    async json() {
      return body;
    },
  };
}

function createBlobResponse(body: string) {
  return {
    ok: true,
    status: 200,
    clone() {
      return createBlobResponse(body);
    },
    async blob() {
      return new Blob([body], { type: "video/mp4" });
    },
  };
}

describe("ScreenPlayer browser runtime", () => {
  let mockCache: MockCache;
  let fetchMock: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    vi.useFakeTimers();
    window.localStorage.clear();

    mockCache = {
      match: vi.fn().mockResolvedValue(null),
      put: vi.fn().mockResolvedValue(undefined),
      keys: vi.fn().mockResolvedValue([]),
      delete: vi.fn().mockResolvedValue(true),
    };

    Object.defineProperty(window, "caches", {
      configurable: true,
      value: {
        open: vi.fn().mockImplementation(async (name: string) => {
          expect(name).toBe(getTvMediaCacheName("screen_001"));
          return mockCache;
        }),
      },
    });

    vi.stubGlobal(
      "fetch",
      vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
        const url = typeof input === "string" ? input : input instanceof URL ? input.pathname : input.url;

        if (url === "/api/tvs/heartbeat") {
          return createJsonResponse({ lastSeen: "2026-03-20T18:00:00.000Z" });
        }

        if (url === "/api/tvs/impression") {
          return createJsonResponse({ impressionId: "impression_001" });
        }

        if (url === "/api/tvs/manifest/screen_001") {
          return createJsonResponse(initialManifest);
        }

        if (url === "/videos/hero.mp4") {
          return createBlobResponse("video");
        }

        throw new Error(`unexpected fetch ${url} ${init?.method ?? "GET"}`);
      })
    );
    fetchMock = global.fetch as typeof fetchMock;

    vi.stubGlobal("URL", {
      ...URL,
      createObjectURL: vi.fn(() => "blob:cached-video"),
      revokeObjectURL: vi.fn(),
    });
  });

  afterEach(() => {
    cleanup();
    vi.useRealTimers();
    vi.unstubAllGlobals();
  });

  async function flushRuntime() {
    await act(async () => {
      await Promise.resolve();
      await Promise.resolve();
    });
  }

  it("prefers cached blob playback when media is cached", async () => {
    mockCache.match.mockResolvedValue(
      createBlobResponse("cached")
    );

    const { container } = render(<ScreenPlayer screenId="screen_001" initialManifest={initialManifest} />);

    await flushRuntime();

    expect(screen.getByText("Playback: cached")).toBeTruthy();
    expect(screen.getByText("Active source: blob")).toBeTruthy();
    expect(screen.getByText("Cache API: available")).toBeTruthy();
    expect(screen.getByText("Cached creatives: 1")).toBeTruthy();
    expect(screen.getByText("Network: online")).toBeTruthy();
    const video = container.querySelector("video");
    expect(video?.getAttribute("src")).toBe("blob:cached-video");
    expect(fetchMock).toHaveBeenCalledWith(
      "/api/tvs/heartbeat",
      expect.objectContaining({ method: "POST" })
    );
  });

  it("keeps cached playback active when manifest refresh fails", async () => {
    mockCache.match.mockResolvedValue(
      createBlobResponse("cached")
    );

    fetchMock.mockImplementation(async (input: RequestInfo | URL) => {
      const url = typeof input === "string" ? input : input instanceof URL ? input.pathname : input.url;

      if (url === "/api/tvs/heartbeat") {
        return createJsonResponse({ lastSeen: "2026-03-20T18:00:00.000Z" });
      }

      if (url === "/api/tvs/impression") {
        return createJsonResponse({ impressionId: "impression_001" });
      }

      if (url === "/api/tvs/manifest/screen_001") {
        throw new Error("manifest offline");
      }

      throw new Error(`unexpected fetch ${url}`);
    });

    const { container } = render(<ScreenPlayer screenId="screen_001" initialManifest={initialManifest} />);

    await flushRuntime();

    await act(async () => {
      vi.advanceTimersByTime(60000);
      await Promise.resolve();
    });
    await flushRuntime();

    const video = container.querySelector("video");
    expect(video?.getAttribute("src")).toBe("blob:cached-video");
    expect(screen.getByText("Playback: cached")).toBeTruthy();
    expect(screen.getByText("Manifest refresh: failed")).toBeTruthy();
  });

  it("queues impression retries on transient failures", async () => {
    fetchMock.mockImplementation(async (input: RequestInfo | URL, init?: RequestInit) => {
      const url = typeof input === "string" ? input : input instanceof URL ? input.pathname : input.url;

      if (url === "/api/tvs/heartbeat") {
        return createJsonResponse({ lastSeen: "2026-03-20T18:00:00.000Z" });
      }

      if (url === "/api/tvs/impression" && init?.method === "POST") {
        return createJsonResponse({ ok: false }, { status: 503 });
      }

      if (url === "/videos/hero.mp4") {
        return createBlobResponse("video");
      }

      if (url === "/api/tvs/manifest/screen_001") {
        return createJsonResponse(initialManifest);
      }

      throw new Error(`unexpected fetch ${url}`);
    });

    render(<ScreenPlayer screenId="screen_001" initialManifest={initialManifest} />);
    await flushRuntime();

    await act(async () => {
      vi.advanceTimersByTime(1500);
      await Promise.resolve();
    });
    await flushRuntime();

    expect(screen.getByText("Queued impressions: 1")).toBeTruthy();
    const storedQueue = window.localStorage.getItem(getTvImpressionQueueStorageKey("screen_001"));
    expect(storedQueue).toBeTruthy();
    expect(JSON.parse(storedQueue ?? "[]")).toHaveLength(1);
  });
});
