import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state";
import { Utils } from "../../platform/misc/utils";
import { UserId } from "../../types/guid";
import { ProviderUserStatusType, ProviderUserType } from "../enums";
import { ProviderData } from "../models/data/provider.data";
import { Provider } from "../models/domain/provider";

import { PROVIDERS, ProviderService } from "./provider.service";

/**
 * It is easier to read arrays than records in code, but we store a record
 * in state. This helper methods lets us build provider arrays in tests
 * and easily map them to records before storing them in state.
 */
function arrayToRecord(input: ProviderData[]): Record<string, ProviderData> {
  if (input == null) {
    return undefined;
  }
  return Object.fromEntries(input?.map((i) => [i.id, i]));
}

/**
 * Builds a simple mock `ProviderData[]` array that can be used in tests
 * to populate state.
 * @param count The number of organizations to populate the list with. The
 * function returns undefined if this is less than 1. The default value is 1.
 * @param suffix A string to append to data fields on each provider.
 * This defaults to the index of the organization in the list.
 * @returns a `ProviderData[]` array that can be used to populate
 * stateProvider.
 */
function buildMockProviders(count = 1, suffix?: string): ProviderData[] {
  if (count < 1) {
    return undefined;
  }

  function buildMockProvider(id: string, name: string): ProviderData {
    const data = new ProviderData({} as any);
    data.id = id;
    data.name = name;

    return data;
  }

  const mockProviders = [];
  for (let i = 0; i < count; i++) {
    const s = suffix ? suffix + i.toString() : i.toString();
    mockProviders.push(buildMockProvider("provider" + s, "provider" + s));
  }

  return mockProviders;
}

describe("PROVIDERS key definition", () => {
  const sut = PROVIDERS;
  it("should deserialize to a proper ProviderData object", async () => {
    const expectedResult: Record<string, ProviderData> = {
      "1": {
        id: "string",
        name: "string",
        status: ProviderUserStatusType.Accepted,
        type: ProviderUserType.ServiceUser,
        enabled: true,
        userId: "string",
        useEvents: true,
      },
    };
    const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult)));
    expect(result).toEqual(expectedResult);
  });
});

describe("ProviderService", () => {
  let providerService: ProviderService;

  const fakeUserId = Utils.newGuid() as UserId;
  let fakeAccountService: FakeAccountService;
  let fakeStateProvider: FakeStateProvider;
  let fakeUserState: FakeSingleUserState<Record<string, ProviderData>>;
  let fakeActiveUserState: FakeActiveUserState<Record<string, ProviderData>>;

  beforeEach(async () => {
    fakeAccountService = mockAccountServiceWith(fakeUserId);
    fakeStateProvider = new FakeStateProvider(fakeAccountService);
    fakeUserState = fakeStateProvider.singleUser.getFake(fakeUserId, PROVIDERS);
    fakeActiveUserState = fakeStateProvider.activeUser.getFake(PROVIDERS);
    providerService = new ProviderService(fakeStateProvider);
  });

  describe("getAll()", () => {
    it("Returns an array of all providers stored in state", async () => {
      const mockData: ProviderData[] = buildMockProviders(5);
      fakeUserState.nextState(arrayToRecord(mockData));
      const providers = await providerService.getAll();
      expect(providers).toHaveLength(5);
      expect(providers).toEqual(mockData.map((x) => new Provider(x)));
    });

    it("Returns an empty array if no providers are found in state", async () => {
      const mockData: ProviderData[] = undefined;
      fakeUserState.nextState(arrayToRecord(mockData));
      const result = await providerService.getAll();
      expect(result).toEqual([]);
    });
  });

  describe("get()", () => {
    it("Returns a single provider from state that matches the specified id", async () => {
      const mockData = buildMockProviders(5);
      fakeUserState.nextState(arrayToRecord(mockData));
      const result = await providerService.get(mockData[3].id);
      expect(result).toEqual(new Provider(mockData[3]));
    });

    it("Returns undefined if the specified provider id is not found", async () => {
      const result = await providerService.get("this-provider-does-not-exist");
      expect(result).toBe(undefined);
    });
  });

  describe("save()", () => {
    it("replaces the entire provider list in state for the active user", async () => {
      const originalData = buildMockProviders(10);
      fakeUserState.nextState(arrayToRecord(originalData));

      const newData = arrayToRecord(buildMockProviders(10, "newData"));
      await providerService.save(newData);

      expect(fakeActiveUserState.nextMock).toHaveBeenCalledWith([fakeUserId, newData]);
    });

    // This is more or less a test for logouts
    it("can replace state with null", async () => {
      const originalData = buildMockProviders(2);
      fakeActiveUserState.nextState(arrayToRecord(originalData));
      await providerService.save(null);

      expect(fakeActiveUserState.nextMock).toHaveBeenCalledWith([fakeUserId, null]);
    });
  });
});
