import test from "node:test";
import assert from "node:assert/strict";
import {
  AnkiJimakuIpcRuntimeOptions,
  registerAnkiJimakuIpcRuntimeService,
} from "./anki-jimaku-service";

interface RuntimeHarness {
  options: AnkiJimakuIpcRuntimeOptions;
  registered: Record<string, (...args: unknown[]) => unknown>;
  state: {
    ankiIntegration: unknown;
    fieldGroupingResolver: ((choice: unknown) => void) | null;
    patches: boolean[];
    broadcasts: number;
    fetchCalls: Array<{ endpoint: string; query?: Record<string, unknown> }>;
    sentCommands: Array<{ command: string[] }>;
  };
}

function createHarness(): RuntimeHarness {
  const state = {
    ankiIntegration: null as unknown,
    fieldGroupingResolver: null as ((choice: unknown) => void) | null,
    patches: [] as boolean[],
    broadcasts: 0,
    fetchCalls: [] as Array<{ endpoint: string; query?: Record<string, unknown> }>,
    sentCommands: [] as Array<{ command: string[] }>,
  };

  const options: AnkiJimakuIpcRuntimeOptions = {
    patchAnkiConnectEnabled: (enabled) => {
      state.patches.push(enabled);
    },
    getResolvedConfig: () => ({}),
    getRuntimeOptionsManager: () => null,
    getSubtitleTimingTracker: () => null,
    getMpvClient: () => ({
      connected: true,
      send: (payload) => {
        state.sentCommands.push(payload);
      },
    }),
    getAnkiIntegration: () => state.ankiIntegration as never,
    setAnkiIntegration: (integration) => {
      state.ankiIntegration = integration;
    },
    showDesktopNotification: () => {},
    createFieldGroupingCallback: () => async () => ({
      keepNoteId: 1,
      deleteNoteId: 2,
      deleteDuplicate: false,
      cancelled: false,
    }),
    broadcastRuntimeOptionsChanged: () => {
      state.broadcasts += 1;
    },
    getFieldGroupingResolver: () => state.fieldGroupingResolver as never,
    setFieldGroupingResolver: (resolver) => {
      state.fieldGroupingResolver = resolver as never;
    },
    parseMediaInfo: () => ({
      title: "video",
      confidence: "high",
      rawTitle: "video",
      filename: "video.mkv",
      season: null,
      episode: null,
    }),
    getCurrentMediaPath: () => "/tmp/video.mkv",
    jimakuFetchJson: async (endpoint, query) => {
      state.fetchCalls.push({ endpoint, query: query as Record<string, unknown> });
      return {
        ok: true,
        data: [
          { id: 1, name: "a" },
          { id: 2, name: "b" },
          { id: 3, name: "c" },
        ] as never,
      };
    },
    getJimakuMaxEntryResults: () => 2,
    getJimakuLanguagePreference: () => "ja",
    resolveJimakuApiKey: async () => "token",
    isRemoteMediaPath: () => false,
    downloadToFile: async (url, destPath) => ({
      ok: true,
      path: `${destPath}:${url}`,
    }),
  };

  let registered: Record<string, (...args: unknown[]) => unknown> = {};
  registerAnkiJimakuIpcRuntimeService(
    options,
    (deps) => {
      registered = deps as unknown as Record<string, (...args: unknown[]) => unknown>;
    },
  );

  return { options, registered, state };
}

test("registerAnkiJimakuIpcRuntimeService provides full handler surface", () => {
  const { registered } = createHarness();
  const expected = [
    "setAnkiConnectEnabled",
    "clearAnkiHistory",
    "respondFieldGrouping",
    "buildKikuMergePreview",
    "getJimakuMediaInfo",
    "searchJimakuEntries",
    "listJimakuFiles",
    "resolveJimakuApiKey",
    "getCurrentMediaPath",
    "isRemoteMediaPath",
    "downloadToFile",
    "onDownloadedSubtitle",
  ];

  for (const key of expected) {
    assert.equal(typeof registered[key], "function", `missing handler: ${key}`);
  }
});

test("setAnkiConnectEnabled disables active integration and broadcasts changes", () => {
  const { registered, state } = createHarness();
  let destroyed = 0;
  state.ankiIntegration = {
    destroy: () => {
      destroyed += 1;
    },
  };

  registered.setAnkiConnectEnabled(false);

  assert.deepEqual(state.patches, [false]);
  assert.equal(destroyed, 1);
  assert.equal(state.ankiIntegration, null);
  assert.equal(state.broadcasts, 1);
});

test("clearAnkiHistory and respondFieldGrouping execute runtime callbacks", () => {
  const { registered, state, options } = createHarness();
  let cleaned = 0;
  let resolvedChoice: unknown = null;
  state.fieldGroupingResolver = (choice) => {
    resolvedChoice = choice;
  };

  const originalGetTracker = options.getSubtitleTimingTracker;
  options.getSubtitleTimingTracker = () =>
    ({ cleanup: () => {
      cleaned += 1;
    } }) as never;

  const choice = {
    keepNoteId: 10,
    deleteNoteId: 11,
    deleteDuplicate: true,
    cancelled: false,
  };
  registered.clearAnkiHistory();
  registered.respondFieldGrouping(choice);

  options.getSubtitleTimingTracker = originalGetTracker;

  assert.equal(cleaned, 1);
  assert.deepEqual(resolvedChoice, choice);
  assert.equal(state.fieldGroupingResolver, null);
});

test("buildKikuMergePreview returns guard error when integration is missing", async () => {
  const { registered } = createHarness();

  const result = await registered.buildKikuMergePreview({
    keepNoteId: 1,
    deleteNoteId: 2,
    deleteDuplicate: false,
  });

  assert.deepEqual(result, {
    ok: false,
    error: "AnkiConnect integration not enabled",
  });
});

test("buildKikuMergePreview delegates to integration when available", async () => {
  const { registered, state } = createHarness();
  const calls: unknown[] = [];
  state.ankiIntegration = {
    buildFieldGroupingPreview: async (
      keepNoteId: number,
      deleteNoteId: number,
      deleteDuplicate: boolean,
    ) => {
      calls.push([keepNoteId, deleteNoteId, deleteDuplicate]);
      return { ok: true };
    },
  };

  const result = await registered.buildKikuMergePreview({
    keepNoteId: 3,
    deleteNoteId: 4,
    deleteDuplicate: true,
  });

  assert.deepEqual(calls, [[3, 4, true]]);
  assert.deepEqual(result, { ok: true });
});

test("searchJimakuEntries caps results and onDownloadedSubtitle sends sub-add to mpv", async () => {
  const { registered, state } = createHarness();

  const searchResult = await registered.searchJimakuEntries({ query: "test" });
  assert.deepEqual(state.fetchCalls, [
    {
      endpoint: "/api/entries/search",
      query: { anime: true, query: "test" },
    },
  ]);
  assert.equal((searchResult as { ok: boolean }).ok, true);
  assert.equal((searchResult as { data: unknown[] }).data.length, 2);

  registered.onDownloadedSubtitle("/tmp/subtitle.ass");
  assert.deepEqual(state.sentCommands, [
    { command: ["sub-add", "/tmp/subtitle.ass", "select"] },
  ]);
});
