import { expect, describe, test, beforeEach, spyOn } from "bun:test";
import { Clipboard } from "../../../src/features/action/Clipboard";
import { BootedDevice } from "../../../src/models";
import { IOSCtrlProxyClient } from "../../../src/features/observe/ios";
import { AndroidCtrlProxyClient } from "../../../src/features/observe/android";
import { FakeIOSCtrlProxy } from "../../fakes/FakeIOSCtrlProxy";
import { FakeAdbClientFactory } from "../../fakes/FakeAdbClientFactory";

describe("Clipboard iOS", () => {
  let clipboard: Clipboard;
  let mockDevice: BootedDevice;
  let fakeIOSCtrlProxy: FakeIOSCtrlProxy;

  beforeEach(() => {
    mockDevice = {
      name: "Test iPhone",
      platform: "ios",
      deviceId: "test-iphone"
    };

    fakeIOSCtrlProxy = new FakeIOSCtrlProxy();

    // Mock IOSCtrlProxyClient.getInstance to return our fake
    spyOn(IOSCtrlProxyClient, "getInstance").mockReturnValue(
      fakeIOSCtrlProxy as unknown as IOSCtrlProxyClient
    );

    clipboard = new Clipboard(mockDevice);
  });

  test("get returns clipboard text", async () => {
    fakeIOSCtrlProxy.setClipboardResult({
      success: true,
      action: "get",
      text: "hello world",
      totalTimeMs: 10,
    });

    const result = await clipboard.execute("get");

    expect(result.success).toBe(true);
    expect(result.action).toBe("get");
    expect(result.text).toBe("hello world");
    expect(result.method).toBe("a11y");
  });

  test("copy sends text to clipboard", async () => {
    fakeIOSCtrlProxy.setClipboardResult({
      success: true,
      action: "copy",
      totalTimeMs: 10,
    });

    const result = await clipboard.execute("copy", "test text");

    expect(result.success).toBe(true);
    expect(result.action).toBe("copy");

    const history = fakeIOSCtrlProxy.getClipboardHistory();
    expect(history).toHaveLength(1);
    expect(history[0].action).toBe("copy");
    expect(history[0].text).toBe("test text");
  });

  test("copy without text returns error", async () => {
    const result = await clipboard.execute("copy");

    expect(result.success).toBe(false);
    expect(result.error).toContain("Text is required");
  });

  test("clear clipboard succeeds", async () => {
    fakeIOSCtrlProxy.setClipboardResult({
      success: true,
      action: "clear",
      totalTimeMs: 10,
    });

    const result = await clipboard.execute("clear");

    expect(result.success).toBe(true);
    expect(result.action).toBe("clear");
  });

  test("paste clipboard succeeds", async () => {
    fakeIOSCtrlProxy.setClipboardResult({
      success: true,
      action: "paste",
      totalTimeMs: 10,
    });

    const result = await clipboard.execute("paste");

    expect(result.success).toBe(true);
    expect(result.action).toBe("paste");
  });

  test("returns error when CtrlProxy fails", async () => {
    fakeIOSCtrlProxy.setClipboardResult({
      success: false,
      action: "get",
      totalTimeMs: 10,
      error: "Clipboard access denied",
    });

    const result = await clipboard.execute("get");

    expect(result.success).toBe(false);
    expect(result.error).toBe("Clipboard access denied");
  });

  test("get returns success with undefined text when clipboard empty", async () => {
    fakeIOSCtrlProxy.setClipboardResult({
      success: true,
      action: "get",
      text: undefined,
      totalTimeMs: 10,
    });

    const result = await clipboard.execute("get");

    expect(result.success).toBe(true);
    expect(result.text).toBeUndefined();
  });
});

describe("Clipboard Android", () => {
  // Regression for https://github.com/kaeawc/auto-mobile/issues/2227.
  // AndroidCtrlProxyClient.getInstance expects an AdbClientFactory and calls
  // `.create(device)` on it. Passing the resolved AdbExecutor instead
  // surfaces in production as `TypeError: <minified>.create is not a function`
  // and silently breaks the a11y clipboard path.
  test("passes the injected AdbClientFactory (not AdbExecutor) to AndroidCtrlProxyClient.getInstance", async () => {
    const factory = new FakeAdbClientFactory();
    const getInstanceSpy = spyOn(AndroidCtrlProxyClient, "getInstance").mockReturnValue({
      requestClipboard: async () => ({ success: true, action: "copy", totalTimeMs: 1 }),
    } as unknown as AndroidCtrlProxyClient);

    const device: BootedDevice = {
      name: "Test Android",
      platform: "android",
      deviceId: "test-android",
    };
    const clipboard = new Clipboard(device, factory);
    await clipboard.execute("copy", "hello");

    expect(getInstanceSpy).toHaveBeenCalled();
    const [, passedFactory] = getInstanceSpy.mock.calls[0] as [BootedDevice, FakeAdbClientFactory];
    expect(typeof passedFactory.create).toBe("function");
    expect(passedFactory).toBe(factory);
    expect(factory.wasCalledForDevice(device.deviceId)).toBe(true);

    getInstanceSpy.mockRestore();
  });
});
