import { armRequest } from "../../../../Utils/arm/request";
import { NetworkType, OsType, SessionType, ShellType } from "../Models/DataModels";
import {
  connectTerminal,
  getUserSettings,
  provisionConsole,
  putEphemeralUserSettings,
  registerCloudShellProvider,
  resizeTerminal,
  verifyCloudShellProviderRegistration,
} from "./CloudShellClient";

// Instead of redeclaring fetch, modify the global context
/* eslint-disable @typescript-eslint/no-namespace */
declare global {
  namespace NodeJS {
    interface Global {
      fetch: jest.Mock;
    }
  }
}
/* eslint-enable @typescript-eslint/no-namespace */

// Define mock endpoint
const MOCK_ARM_ENDPOINT = "https://mock-management.azure.com";

// Mock dependencies
jest.mock("uuid", () => ({
  v4: jest.fn().mockReturnValue("mocked-uuid"),
}));

jest.mock("../../../../ConfigContext", () => ({
  configContext: {
    ARM_ENDPOINT: "https://mock-management.azure.com",
  },
}));

jest.mock("../../../../UserContext", () => ({
  userContext: {
    authorizationToken: "Bearer mock-token",
  },
}));

jest.mock("../../../../Utils/arm/request");

jest.mock("../Utils/CommonUtils", () => ({
  getLocale: jest.fn().mockReturnValue("en-US"),
}));

// Properly mock fetch with correct typings
const mockJsonPromise = jest.fn();
global.fetch = jest.fn().mockImplementationOnce(() => {
  return {
    ok: true,
    status: 200,
    json: mockJsonPromise,
    text: jest.fn().mockResolvedValue(""),
    headers: new Headers(),
  } as unknown as Promise<Response>;
}) as jest.Mock;

describe("CloudShellClient", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockJsonPromise.mockClear();
  });

  // Reset all mocks after all tests
  afterAll(() => {
    jest.resetAllMocks();
    jest.restoreAllMocks();
    if (global.fetch) {
      delete global.fetch;
    }
  });

  describe("getUserSettings", () => {
    it("should call armRequest with correct parameters and return settings", async () => {
      const mockSettings = { properties: { preferredLocation: "eastus" } };
      (armRequest as jest.Mock).mockResolvedValueOnce(mockSettings);

      const result = await getUserSettings();

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "/providers/Microsoft.Portal/userSettings/cloudconsole",
        method: "GET",
        apiVersion: "2023-02-01-preview",
      });
      expect(result).toEqual(mockSettings);
    });

    it("should handle errors when settings retrieval fails", async () => {
      const mockError = new Error("Failed to get user settings");
      (armRequest as jest.Mock).mockRejectedValueOnce(mockError);

      await expect(getUserSettings()).rejects.toThrow("Failed to get user settings");

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "/providers/Microsoft.Portal/userSettings/cloudconsole",
        method: "GET",
        apiVersion: "2023-02-01-preview",
      });
    });
  });

  describe("putEphemeralUserSettings", () => {
    it("should call armRequest with default network settings", async () => {
      const mockResponse = { id: "settings-id" };
      (armRequest as jest.Mock).mockResolvedValueOnce(mockResponse);

      const result = await putEphemeralUserSettings("sub-id", "eastus");

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "/providers/Microsoft.Portal/userSettings/cloudconsole",
        method: "PUT",
        apiVersion: "2023-02-01-preview",
        body: {
          properties: {
            preferredOsType: OsType.Linux,
            preferredShellType: ShellType.Bash,
            preferredLocation: "eastus",
            networkType: NetworkType.Default,
            sessionType: SessionType.Ephemeral,
            userSubscription: "sub-id",
            vnetSettings: {},
          },
        },
        timeoutMs: 30000,
      });
      expect(result).toEqual(mockResponse);
    });

    it("should call armRequest with isolated network settings", async () => {
      const mockVNetSettings = { subnetId: "test-subnet" };
      const mockResponse = { id: "settings-id" };
      (armRequest as jest.Mock).mockResolvedValueOnce(mockResponse);

      await putEphemeralUserSettings("sub-id", "eastus", mockVNetSettings);

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "/providers/Microsoft.Portal/userSettings/cloudconsole",
        method: "PUT",
        apiVersion: "2023-02-01-preview",
        body: {
          properties: {
            preferredOsType: OsType.Linux,
            preferredShellType: ShellType.Bash,
            preferredLocation: "eastus",
            networkType: NetworkType.Isolated,
            sessionType: SessionType.Ephemeral,
            userSubscription: "sub-id",
            vnetSettings: mockVNetSettings,
          },
        },
        timeoutMs: 30000,
      });
    });

    it("should handle errors when updating settings fails", async () => {
      const mockError = new Error("Failed to update user settings");
      (armRequest as jest.Mock).mockRejectedValueOnce(mockError);

      await expect(putEphemeralUserSettings("sub-id", "eastus")).rejects.toThrow("Failed to update user settings");

      expect(armRequest).toHaveBeenCalled();
    });
  });

  describe("verifyCloudShellProviderRegistration", () => {
    it("should call armRequest with correct parameters", async () => {
      const mockResponse = { registrationState: "Registered" };
      (armRequest as jest.Mock).mockResolvedValueOnce(mockResponse);

      const result = await verifyCloudShellProviderRegistration("sub-id");

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "/subscriptions/sub-id/providers/Microsoft.CloudShell",
        method: "GET",
        apiVersion: "2022-12-01",
      });
      expect(result).toEqual(mockResponse);
    });

    it("should handle errors when verification fails", async () => {
      const mockError = new Error("Failed to verify provider registration");
      (armRequest as jest.Mock).mockRejectedValueOnce(mockError);

      await expect(verifyCloudShellProviderRegistration("sub-id")).rejects.toThrow(
        "Failed to verify provider registration",
      );

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "/subscriptions/sub-id/providers/Microsoft.CloudShell",
        method: "GET",
        apiVersion: "2022-12-01",
      });
    });
  });

  describe("registerCloudShellProvider", () => {
    it("should call armRequest with correct parameters", async () => {
      const mockResponse = { operationId: "op-id" };
      (armRequest as jest.Mock).mockResolvedValueOnce(mockResponse);

      const result = await registerCloudShellProvider("sub-id");

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "/subscriptions/sub-id/providers/Microsoft.CloudShell/register",
        method: "POST",
        apiVersion: "2022-12-01",
        timeoutMs: 30000,
      });
      expect(result).toEqual(mockResponse);
    });

    it("should handle errors when registration fails", async () => {
      const mockError = new Error("Failed to register provider");
      (armRequest as jest.Mock).mockRejectedValueOnce(mockError);

      await expect(registerCloudShellProvider("sub-id")).rejects.toThrow("Failed to register provider");

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "/subscriptions/sub-id/providers/Microsoft.CloudShell/register",
        method: "POST",
        apiVersion: "2022-12-01",
        timeoutMs: 30000,
      });
    });
  });

  describe("provisionConsole", () => {
    it("should call armRequest with correct parameters", async () => {
      const mockResponse = { uri: "https://shell.azure.com/console123" };
      (armRequest as jest.Mock).mockResolvedValueOnce(mockResponse);

      const result = await provisionConsole("eastus");

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "providers/Microsoft.Portal/consoles/default",
        method: "PUT",
        apiVersion: "2023-02-01-preview",
        customHeaders: {
          "x-ms-console-preferred-location": "eastus",
        },
        body: {
          properties: {
            osType: OsType.Linux,
          },
        },
        timeoutMs: 30000,
      });
      expect(result).toEqual(mockResponse);
    });

    it("should handle errors when console provisioning fails", async () => {
      const mockError = new Error("Failed to provision console");
      (armRequest as jest.Mock).mockRejectedValueOnce(mockError);

      await expect(provisionConsole("eastus")).rejects.toThrow("Failed to provision console");

      expect(armRequest).toHaveBeenCalledWith({
        host: MOCK_ARM_ENDPOINT,
        path: "providers/Microsoft.Portal/consoles/default",
        method: "PUT",
        apiVersion: "2023-02-01-preview",
        customHeaders: {
          "x-ms-console-preferred-location": "eastus",
        },
        body: {
          properties: {
            osType: OsType.Linux,
          },
        },
        timeoutMs: 30000,
      });
    });
  });

  describe("connectTerminal", () => {
    it("should call fetch with correct parameters", async () => {
      const consoleUri = "https://shell.azure.com/console123";
      const size = { rows: 24, cols: 80 };
      const mockTerminalResponse = { id: "terminal-id", socketUri: "wss://shell.azure.com/socket" };

      // Setup the mock response
      mockJsonPromise.mockResolvedValueOnce(mockTerminalResponse);

      const result = await connectTerminal(consoleUri, size);

      expect(global.fetch).toHaveBeenCalledWith(
        "https://shell.azure.com/console123/terminals?cols=80&rows=24&version=2019-01-01&shell=bash",
        {
          method: "POST",
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
            "Content-Length": "2",
            Authorization: "Bearer mock-token",
            "x-ms-client-request-id": "mocked-uuid",
            "Accept-Language": "en-US",
          },
          body: "{}",
        },
      );
      expect(mockJsonPromise).toHaveBeenCalled();
      expect(result).toEqual(mockTerminalResponse);
    });

    it("should handle errors when terminal connection fails", async () => {
      const consoleUri = "https://shell.azure.com/console123";
      const size = { rows: 24, cols: 80 };

      // Mock fetch to return a failed response
      global.fetch = jest.fn().mockImplementationOnce(() => {
        return {
          ok: false,
          status: 500,
          statusText: "Internal Server Error",
          json: jest.fn().mockRejectedValue(new Error("Failed to parse JSON")),
          text: jest.fn().mockResolvedValue("Server Error"),
          headers: new Headers(),
        } as unknown as Promise<Response>;
      });

      await expect(connectTerminal(consoleUri, size)).rejects.toThrow(
        "Failed to connect to terminal: 500 Internal Server Error",
      );

      expect(global.fetch).toHaveBeenCalledWith(
        "https://shell.azure.com/console123/terminals?cols=80&rows=24&version=2019-01-01&shell=bash",
        expect.any(Object),
      );
    });
  });

  describe("resizeTerminal", () => {
    it("should call fetch with correct parameters", async () => {
      const consoleUri = "https://shell.azure.com/console123";
      const terminalId = "terminal-id";
      const size = { rows: 40, cols: 120 };

      global.fetch = jest.fn().mockImplementationOnce(() => {
        return {
          ok: true,
          status: 200,
          json: jest.fn().mockResolvedValue({}),
          text: jest.fn().mockResolvedValue(""),
          headers: new Headers(),
        } as unknown as Promise<Response>;
      });

      await resizeTerminal(consoleUri, terminalId, size);

      expect(global.fetch).toHaveBeenCalledWith(
        "https://shell.azure.com/console123/terminals/terminal-id/size?cols=120&rows=40&version=2019-01-01",
        {
          method: "POST",
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
            "Content-Length": "2",
            Authorization: "Bearer mock-token",
            "x-ms-client-request-id": "mocked-uuid",
            "Accept-Language": "en-US",
          },
          body: "{}",
        },
      );
    });

    it("should handle errors when terminal resize fails", async () => {
      const consoleUri = "https://shell.azure.com/console123";
      const terminalId = "terminal-id";
      const size = { rows: 40, cols: 120 };

      global.fetch = jest.fn().mockImplementationOnce(() => {
        return {
          ok: false,
          status: 500,
          statusText: "Internal Server Error",
          json: jest.fn().mockRejectedValue(new Error("Failed to parse JSON")),
          text: jest.fn().mockResolvedValue("Server Error"),
          headers: new Headers(),
        } as unknown as Promise<Response>;
      });

      await expect(resizeTerminal(consoleUri, terminalId, size)).rejects.toThrow(
        "Failed to resize terminal: 500 Internal Server Error",
      );

      expect(global.fetch).toHaveBeenCalledWith(
        "https://shell.azure.com/console123/terminals/terminal-id/size?cols=120&rows=40&version=2019-01-01",
        expect.any(Object),
      );
    });
  });
});
