import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { registerAppTools, resetListAppsToolDependencies, setListAppsToolDependencies } from "../../src/server/appTools";
import { APP_RESOURCE_TEMPLATES, APPS_RESOURCE_URIS } from "../../src/server/appResources";
import { ToolRegistry } from "../../src/server/toolRegistry";
import { FakeToolUtils } from "../fakes/FakeToolUtils";
import { FakeTimer } from "../fakes/FakeTimer";

const resolveWithFakeTimer = async <T>(
  promise: Promise<T>,
  timer: FakeTimer,
  stepMs: number = 1
): Promise<T> => {
  let settled = false;
  let result: T | undefined;
  let error: unknown;

  promise
    .then(value => {
      settled = true;
      result = value;
    })
    .catch(caught => {
      settled = true;
      error = caught;
    });

  let steps = 0;
  while (!settled) {
    if (
      timer.getPendingTimeoutCount() > 0 ||
      timer.getPendingIntervalCount() > 0 ||
      timer.getPendingSleepCount() > 0
    ) {
      timer.advanceTime(stepMs);
    }
    await new Promise(resolve => setImmediate(resolve));
    steps += 1;
    if (steps > 100) {
      throw new Error("FakeTimer pump exceeded max steps");
    }
  }

  if (error) {
    throw error;
  }
  return result as T;
};

describe("listApps tool", () => {
  beforeEach(() => {
    ToolRegistry.clearTools();
    resetListAppsToolDependencies();
    registerAppTools();
  });

  afterEach(() => {
    ToolRegistry.clearTools();
    resetListAppsToolDependencies();
  });

  test("registers listApps tool with a permissive schema", () => {
    const tool = ToolRegistry.getTool("listApps");
    expect(tool).toBeDefined();
    expect(() => tool!.schema.parse({})).not.toThrow();
    expect(() => tool!.schema.parse({ deviceId: "device-123" })).not.toThrow();
  });

  test("returns MCP resource guidance using a fake formatter and FakeTimer", async () => {
    const tool = ToolRegistry.getTool("listApps");
    expect(tool).toBeDefined();

    const fakeToolUtils = new FakeToolUtils();
    setListAppsToolDependencies({ toolResponseFormatter: fakeToolUtils });

    const fakeTimer = new FakeTimer();

    const result = await resolveWithFakeTimer(tool!.handler({}), fakeTimer);

    expect(fakeToolUtils.getJSONResponseCount()).toBe(1);
    const payload = fakeToolUtils.getLastJSONResponse();
    expect(payload).toEqual({
      message: "To list installed apps, follow this workflow:\n\n" +
        "1. Get available devices:\n" +
        "   Read resource: automobile:devices/booted\n\n" +
        "2. List apps for a specific device (using deviceId from step 1):\n" +
        "   Read resource: automobile:devices/{deviceId}/apps\n" +
        "   Or query format: automobile:apps?deviceId={deviceId}\n\n" +
        "Optional query filters:\n" +
        "  - type=user|system (default: user)\n" +
        "  - search=<term> (filter by package name)\n" +
        "  - profile=<userId> (filter by user profile)\n\n" +
        "Example: automobile:apps?deviceId=emulator-5554&type=system&search=google",
      resources: [
        "automobile:devices/booted",
        APP_RESOURCE_TEMPLATES.DEVICE_APPS,
        APPS_RESOURCE_URIS.BASE + "?deviceId={deviceId}"
      ],
      note: "All resource URIs use the 'automobile:' prefix. URIs like 'android://apps' are not supported."
    });

    const content = result.content?.[0];
    expect(content?.type).toBe("text");
    expect(content?.text).toBeDefined();
    expect(JSON.parse(content!.text)).toEqual(payload);
  });
});

describe("app permission tools", () => {
  beforeEach(() => {
    ToolRegistry.clearTools();
    resetListAppsToolDependencies();
    registerAppTools();
  });

  afterEach(() => {
    ToolRegistry.clearTools();
    resetListAppsToolDependencies();
  });

  test("registers cross-platform set/query permission tools", () => {
    const setAppTool = ToolRegistry.getTool("setAppPermissions");
    const getAppTool = ToolRegistry.getTool("getAppPermissions");

    expect(setAppTool).toBeDefined();
    expect(setAppTool?.requiresDevice).toBe(true);
    expect(() => setAppTool!.schema.parse({
      appId: "com.example.app",
      permissions: ["camera"],
      action: "grant"
    })).not.toThrow();
    expect(() => setAppTool!.schema.parse({
      appId: "com.example.app",
      permissions: []
    })).toThrow();
    expect(() => setAppTool!.schema.parse({
      appId: "com.example.app",
      notificationPolicyAccess: true,
      scheduleExactAlarm: "allow"
    })).not.toThrow();
    expect(() => setAppTool!.schema.parse({
      appId: "com.example.app",
      permissions: ["android.permission.POST_NOTIFICATIONS"],
      userId: 10,
    })).not.toThrow();

    expect(getAppTool).toBeDefined();
    expect(getAppTool?.requiresDevice).toBe(true);
    expect(() => getAppTool!.schema.parse({
      appId: "com.example.app",
      permissions: ["camera"]
    })).not.toThrow();
    expect(() => getAppTool!.schema.parse({
      appId: "com.example.app"
    })).not.toThrow();
  });

  test("does not register platform-named permission tools", () => {
    expect(ToolRegistry.getTool("grantAndroidPermissions")).toBeUndefined();
    expect(ToolRegistry.getTool("setAndroidNotificationPolicyAccess")).toBeUndefined();
    expect(ToolRegistry.getTool("setAndroidScheduleExactAlarmAppOp")).toBeUndefined();
    expect(ToolRegistry.getTool("grantIosSimulatorPermissions")).toBeUndefined();
    expect(ToolRegistry.getTool("setIosSimulatorPermissions")).toBeUndefined();
    expect(ToolRegistry.getTool("getIosSimulatorPermissions")).toBeUndefined();
  });
});
