import { Chance } from 'chance';
import { whenRequest } from '@wix/fe-essentials/http-client/testkit/client';
import {
  listTags,
  createTag,
  updateTag,
  deleteTag,
  listTagsForAccount,
  createTagForAccount,
} from '@wix/bex-utils/@wix/ambassador-os-tags-v1-tag/http';
import { Tag as TagType } from '@wix/bex-utils/@wix/ambassador-os-tags-v1-tag/types';
import { InMemoryBackend } from '../testkit/backend';
import { createHttpErrorMock } from './HttpErrorMocks';
import { ExpectedTestError } from '../__tests__/utils/ExpectedTestError';

class ServerError extends ExpectedTestError {
  readonly code: string;

  constructor(code: string, message: string) {
    super(message);
    this.code = code;
  }
}

const chance = new Chance();

const aTag = (overrides: Partial<TagType> = {}): TagType => {
  return {
    id: chance.guid(),
    name: chance.word({ length: 5 }),
    createdDate: chance.date({
      min: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000),
      max: new Date(),
    }) as Date,
    ...overrides,
  } as TagType;
};

export const aTagsArray = (count: number) =>
  Array.from({ length: count }, () => aTag());

export const generateInvalidLongName = (length: number) =>
  chance.string({ length });
export const TAGS = aTagsArray(2);

export const withListTags = (tags?: TagType[], fn = listTags) =>
  whenRequest(fn)
    .reply(200, async () => {
      const data = {
        tags: tags || TAGS,
      };

      return data;
    })
    .persist();

export const withListTagsForAccount = (tags?: TagType[]) =>
  withListTags(tags, listTagsForAccount);

const createTagResponseMock = (tag: TagType) => ({
  __applicationErrorsType: {},
  tag: { createdDate: new Date(), updatedDate: new Date(), ...tag },
});

export const withCreateTag = (mock?: jest.Mock<any, any>) =>
  whenRequest(createTag)
    .reply(
      200,
      mock ||
        (async ({ tag }) => {
          return createTagResponseMock(tag);
        }),
    )
    .persist();

export const withCreateTagForAccount = (mock?: jest.Mock<any, any>) =>
  whenRequest(createTagForAccount)
    .reply(
      200,
      mock ||
        (async ({ tag }) => {
          return createTagResponseMock(tag);
        }),
    )
    .persist();

export const withUpdateTag = () =>
  whenRequest(updateTag)
    .reply(200, async (tag) => {
      return tag;
    })
    .persist();

export const withDeleteTag = () =>
  whenRequest(deleteTag)
    .reply(200, async () => {
      return {};
    })
    .persist();

export const getListTagsErrorMock = ({
  code = 'ERR_NETWORK',
  status = 429,
}: {
  code?: string;
  status?: number;
} = {}) =>
  whenRequest(listTags)
    .reply(200, async () => {
      return new Promise((_resolve, reject) => {
        window.setTimeout(() => {
          reject(createHttpErrorMock({ code, status }));
        }, 500);
      });
    })
    .persist();

export const getListTagsForAccountErrorMock = ({
  code = 'ERR_NETWORK',
  status = 429,
}: {
  code?: string;
  status?: number;
} = {}) =>
  whenRequest(listTagsForAccount)
    .reply(200, async () => {
      return new Promise((_resolve, reject) => {
        setTimeout(() => {
          reject(createHttpErrorMock({ code, status }));
        }, 500);
      });
    })
    .persist();

export const getCreateTagErrorMock = ({
  code = 'ERR_NETWORK',
  status = 429,
  requestId,
}: {
  code?: string;
  status?: number;
  requestId?: string;
} = {}) =>
  whenRequest(createTag)
    .reply(200, async () => {
      return new Promise((_resolve, reject) => {
        window.setTimeout(() => {
          reject(createHttpErrorMock({ code, status, requestId }));
        }, 500);
      });
    })
    .persist();

export const getCreateTagForAccountErrorMock = ({
  code = 'ERR_NETWORK',
  status = 429,
}: {
  code?: string;
  status?: number;
} = {}) =>
  whenRequest(createTagForAccount)
    .reply(200, async () => {
      return new Promise((_resolve, reject) => {
        window.setTimeout(() => {
          reject(createHttpErrorMock({ code, status }));
        }, 500);
      });
    })
    .persist();

export const getUpdateTagErrorMock = ({
  code,
  status,
}: {
  code?: string;
  status?: number;
} = {}) =>
  whenRequest(updateTag)
    .reply(200, async () => {
      return new Promise((_resolve, reject) => {
        window.setTimeout(() => {
          reject(createHttpErrorMock({ code, status }));
        }, 500);
      });
    })
    .persist();

export const getDeleteTagErrorMock = (code: string = 'ERR_NETWORK') =>
  whenRequest(deleteTag)
    .reply(200, async () => {
      return new Promise((_resolve, reject) => {
        window.setTimeout(() => {
          reject(new ServerError(code, 'error'));
        }, 500);
      });
    })
    .persist();

export default function createTagImdb({
  createdDate = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000),
  updatedDate = createdDate,
  fqdn = 'wix.patterns.dummyservice.v1.dummy_entity',
  ...overrides
}: Partial<TagType> = {}): TagType {
  return {
    createdDate,
    updatedDate,
    fqdn,
    ...overrides,
  };
}

export function createBackend(tags: TagType[]) {
  return new InMemoryBackend<TagType>({
    total: tags.length,
    paginationMode: 'offset',
    delay: { min: 100, max: 2000 },
    createOne: (index: number) => createTagImdb(tags[index]),
    predicate: (query) => {
      const filters = query.filters ?? {};
      const { fqdn } = filters;
      return (item) => item.fqdn === fqdn;
    },
    orderBy: (query) => {
      return [...(query.sort ?? [])];
    },
    itemKey: (item) => item.id!,
  });
}

export const tagsAPIHttpClientMocks = (tags: TagType[]) => {
  const tagsBackend = createBackend(tags);
  return [
    whenRequest(listTags)
      .reply(200, async ({ fqdn }) => {
        return tagsBackend
          .fetchData({
            filters: {
              fqdn,
            },
            limit: tags.length,
          })
          .then(({ items }) => {
            if (fqdn === 'wix.patterns.dummyservice.v1.empty') {
              return { tags: [] };
            } else {
              return { tags: items };
            }
          });
      })
      .persist(),
    whenRequest(createTag)
      .reply(200, ({ tag }) => {
        tagsBackend.push([tag]);
        return {
          __applicationErrorsType: {},
          tag,
        };
      })
      .persist(),
    whenRequest(updateTag)
      .reply(200, ({ tag }) => {
        tagsBackend.update(tag);
        return { tag };
      })
      .persist(),
    whenRequest(deleteTag)
      .reply(200, ({ tagId }) => {
        tagsBackend.map.delete(tagId);
      })
      .persist(),
  ];
};
