import { afterEach, beforeEach, describe } from "vitest";
import { test as baseTest } from "../../../../test/useMSW.js";
import type { EFConfiguration } from "../../../gui/EFConfiguration.js";
import "../../../gui/EFPreview.js";
import "../../EFTimegroup.js";
import type { EFTimegroup } from "../../EFTimegroup.js";
import "../../EFVideo.js";
import type { EFVideo } from "../../EFVideo.js";

const test = baseTest.extend<{
  timegroup: EFTimegroup;
  video: EFVideo;
  configuration: EFConfiguration;
}>({
  timegroup: async ({}, use) => {
    const timegroup = document.createElement("ef-timegroup");
    timegroup.setAttribute("mode", "sequence");
    timegroup.setAttribute("id", "test-timegroup"); // Required for localStorage key
    timegroup.style.cssText =
      "position: relative; height: 500px; width: 1000px; overflow: hidden; background-color: rgb(100 116 139);";
    await use(timegroup);
  },
  configuration: async ({ expect }, use) => {
    const configuration = document.createElement("ef-configuration");
    configuration.innerHTML = `<h1 style="font: 10px monospace">${expect.getState().currentTestName}</h1>`;
    // Use integrated proxy server (same host/port as test runner)
    const apiHost = `${window.location.protocol}//${window.location.host}`;
    configuration.setAttribute("api-host", apiHost);
    configuration.apiHost = apiHost;
    document.body.appendChild(configuration);
    await use(configuration);
  },
  video: async ({ configuration, timegroup }, use) => {
    const video = document.createElement("ef-video");
    video.id = "bars-n-tone2";
    video.src = "http://web:3000/head-moov-480p.mp4"; // Real video from working simple-demo
    video.style.cssText =
      "width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0;";

    // Create the exact structure from simple-demo.html
    const innerTimegroup = document.createElement("ef-timegroup");
    innerTimegroup.mode = "contain";
    innerTimegroup.style.cssText =
      "position: absolute; width: 100%; height: 100%;";
    innerTimegroup.append(video);
    timegroup.append(innerTimegroup);
    configuration.append(timegroup);

    await use(video);
  },
});

/**
 * Regression test for chunk boundary seeking issue
 *
 * Root cause: 32ms coordination gap between PlaybackController and audio track boundaries
 * - PlaybackController seeks to chunk boundary: 4000ms
 * - Audio track actually starts at: 4032ms
 * - Error: "Seek time 4000ms is outside track range [4032ms, 6016ms]"
 *
 * This occurs during active playbook and browser reloads at 4s mark.
 * Fix: Coordinate chunk boundaries or add tolerance for small gaps.
 */
describe("Audio Seek Task - Chunk Boundary Regression Test", () => {
  beforeEach(() => {
    // Clean up DOM and localStorage
    while (document.body.children.length) {
      document.body.children[0]?.remove();
    }
    localStorage.clear();
  });

  afterEach(async () => {
    // Clean up any remaining elements
    const videos = document.querySelectorAll("ef-video");
    for (const video of videos) {
      video.remove();
    }
  });

  test.skip("should not throw RangeError when seeking to exact 4000ms during playback", async ({
    // SKIP: audioSeekTask is not part of the audio rendering pipeline
    video,
    timegroup,
    expect,
  }) => {
    await video.mediaEngineTask.taskComplete;
    await video.audioInputTask.taskComplete;

    // Simulate active playback - start playing from beginning
    timegroup.currentTimeMs = 0;
    await video.audioSeekTask.taskComplete;

    // Now seek to the exact problematic time that causes:
    // "Seek time 4000ms is outside track range [4032ms, 6016ms]"
    const exactChunkBoundary = 4000;
    timegroup.currentTimeMs = exactChunkBoundary;

    // Should not throw RangeError due to track range mismatch
    await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
  });

  test.skip("should not throw RangeError during progressive playback across segments", async ({
    // SKIP: audioSeekTask is not part of the audio rendering pipeline
    video,
    timegroup,
    expect,
  }) => {
    await video.mediaEngineTask.taskComplete;
    await video.audioInputTask.taskComplete;

    // Simulate progressive playback that loads segments on demand
    // Start at 3500ms to be just before the 4-second boundary
    timegroup.currentTimeMs = 3500;
    await video.audioSeekTask.taskComplete;

    // Now cross the 4-second chunk boundary where track range issues occur
    // This should trigger the state where track range is [4032ms, 6016ms]
    // but we're seeking to 4000ms
    timegroup.currentTimeMs = 4000.000000000001; // The exact error from logs

    // Should not throw "Seek time 4000.000000000001ms is outside track range [4032ms, 6016ms]"
    await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
  });

  test.skip("should not throw RangeError when localStorage restoration causes 0ms to 4000ms race condition", async ({
    // SKIP: audioSeekTask is not part of the audio rendering pipeline
    video,
    timegroup,
    expect,
  }) => {
    // REPRODUCE THE RACE CONDITION: Simulate localStorage having "4.0"
    // This mimics the exact simple-demo.html scenario where:
    // 1. Media loads with assumption of currentTimeMs = 0
    // 2. localStorage restores currentTime to 4.0 seconds
    // 3. Seeking 4000ms in segments loaded for 0ms range triggers RangeError

    // Set localStorage BEFORE media finishes initializing
    if (timegroup.id) {
      localStorage.setItem(`ef-timegroup-${timegroup.id}`, "4.0");
    }

    // Wait for media engine but NOT for full initialization
    await video.mediaEngineTask.taskComplete;

    // Now trigger the localStorage restoration that happens in waitForMediaDurations().then()
    // This will load currentTime = 4.0 from localStorage, jumping from 0ms to 4000ms
    timegroup.currentTime = timegroup.maybeLoadTimeFromLocalStorage();

    // This should trigger: "Seek time 4000ms is outside track range [Yms, Zms]"
    // because segments were loaded for 0ms but we're now seeking 4000ms
    await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
  });

  test.skip("should not throw RangeError when forced segment coordination mismatch occurs", async ({
    // SKIP: audioSeekTask is not part of the audio rendering pipeline
    video,
    timegroup,
    expect,
  }) => {
    await video.mediaEngineTask.taskComplete;

    // FORCE SPECIFIC SEGMENT LOADING: Load a segment for 8000ms (segment 5)
    timegroup.currentTimeMs = 8000;
    await video.audioSegmentIdTask.taskComplete;
    await video.audioSegmentFetchTask.taskComplete;
    await video.audioInputTask.taskComplete;

    // Verify we have segment 5 loaded (8000ms / 15000ms = segment 1, but 1-based = segment 1...
    // Actually 8000ms maps to segment 5 based on the actual segment calculation)
    const segmentId = video.audioSegmentIdTask.value;
    expect(segmentId).toBe(4);

    // Now seek to a time in a different segment to test coordination
    timegroup.currentTimeMs = 4000;

    // This tests the fundamental segment coordination issue:
    // - We loaded segment 5 for 8000ms
    // - Now seeking to 4000ms which should be in a different segment
    // - Tests that seek doesn't fail due to segment boundary coordination
    await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
  });

  test.skip("should not throw RangeError when rapidly crossing segment boundaries", async ({
    // SKIP: audioSeekTask is not part of the audio rendering pipeline
    video,
    timegroup,
    expect,
  }) => {
    await video.mediaEngineTask.taskComplete;

    // RAPID BOUNDARY CROSSING: This tests timing-sensitive segment coordination
    const boundaries = [1000, 4000, 8000, 3000, 7000]; // Jump around within segment 1

    for (const timeMs of boundaries) {
      timegroup.currentTimeMs = timeMs;
      // Don't await - test rapid succession to trigger coordination issues
    }

    // Final seek - this should not throw even after rapid boundary crossing
    timegroup.currentTimeMs = 4000;
    await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
  });
});
