/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { differenceInSeconds } from '@web-stories-wp/date';

/**
 * Internal dependencies
 */
import { ApiContext } from '../app/api/apiProvider';
import { defaultStoriesState } from '../app/reducer/stories';
import {
  publisherLogoIds as fillerPublisherLogoIds,
  rawPublisherLogos,
} from '../dataUtils/formattedPublisherLogos';
import formattedStoriesArray from '../dataUtils/formattedStoriesArray';
import formattedTemplatesArray from '../dataUtils/formattedTemplatesArray';
import { STORY_STATUSES, STORY_SORT_OPTIONS } from '../constants/stories';

/* eslint-disable jasmine/no-unsafe-spy */
export default function ApiProviderFixture({ children }) {
  const [media, setMediaState] = useState(getMediaState());
  const [settings, setSettingsState] = useState(getSettingsState());
  const [stories, setStoriesState] = useState(getStoriesState());
  const [templates, setTemplatesState] = useState(getTemplatesState());
  const [currentUser, setCurrentUser] = useState(getCurrentUserState());

  const settingsApi = useMemo(
    () => ({
      fetchSettings: () =>
        setSettingsState((currentState) => fetchSettings(currentState)),
      updateSettings: (updates) =>
        setSettingsState((currentState) =>
          updateSettings(updates, currentState)
        ),
    }),
    []
  );

  const mediaApi = useMemo(
    () => ({
      uploadMedia: jasmine.createSpy('uploadMedia'),
      fetchMediaById: (newIds) =>
        setMediaState((currentState) => fetchMediaById(newIds, currentState)),
    }),
    []
  );

  const storyApi = useMemo(
    () => ({
      duplicateStory: (story) =>
        setStoriesState((currentState) => duplicateStory(story, currentState)),
      fetchStories: (...args) =>
        setStoriesState((currentState) => fetchStories(...args, currentState)),
      clearStoryPreview: () =>
        setStoriesState((currentState) => clearStoryPreview(currentState)),
      createStoryPreview: () =>
        setStoriesState((currentState) => createStoryPreview(currentState)),
      createStoryFromTemplate: jasmine.createSpy('createStoryFromTemplate'),
      trashStory: (story) =>
        setStoriesState((currentState) => trashStory(story, currentState)),
      updateStory: (story) =>
        setStoriesState((currentState) => updateStory(story, currentState)),
    }),
    []
  );

  const templateApi = useMemo(
    () => ({
      bookmarkTemplateById: jasmine.createSpy('bookmarkTemplateById'),
      createTemplateFromStory: jasmine.createSpy('createTemplateFromStory'),
      fetchBookmarkedTemplates: jasmine.createSpy('fetchBookmarkedTemplates'),
      fetchExternalTemplates: () =>
        setTemplatesState((currentState) =>
          fetchExternalTemplates(currentState)
        ),
      fetchExternalTemplateById: (id) =>
        fetchExternalTemplateById(id, templates),
      fetchMyTemplates: jasmine.createSpy('fetchMyTemplates'),
      fetchMyTemplateById: (id) => fetchExternalTemplateById(id, templates),
      fetchRelatedTemplates: (currentTemplateId) =>
        fetchRelatedTemplates(currentTemplateId, templates),
      fetchSavedTemplates: jasmine.createSpy('fetchSavedTemplates'),
    }),
    [templates]
  );

  const usersApi = useMemo(
    () => ({
      fetchCurrentUser: jasmine.createSpy('fetchCurrentUser'),
      toggleWebStoriesTrackingOptIn: () =>
        setCurrentUser(toggleOptInTracking(currentUser)),
    }),
    [currentUser]
  );

  const value = useMemo(
    () => ({
      state: {
        media,
        settings,
        stories,
        templates,
        currentUser,
      },
      actions: {
        mediaApi,
        settingsApi,
        storyApi,
        templateApi,
        usersApi,
      },
    }),
    [
      media,
      settings,
      stories,
      templates,
      currentUser,
      mediaApi,
      settingsApi,
      storyApi,
      templateApi,
      usersApi,
    ]
  );

  return <ApiContext.Provider value={value}>{children}</ApiContext.Provider>;
}
/* eslint-enable jasmine/no-unsafe-spy */

ApiProviderFixture.propTypes = {
  children: PropTypes.node,
};

function getMediaState() {
  return {
    error: {},
    isLoading: false,
    newlyCreatedMediaIds: [],
    mediaById: rawPublisherLogos,
  };
}

function fetchMediaById(newIds, currentState) {
  const mediaState = { ...(currentState || getMediaState()) };
  // TODO handle adding Ids
  return mediaState;
}

function getSettingsState() {
  return {
    error: {},
    googleAnalyticsId: '',
    publisherLogoIds: [],
    activePublisherLogoId: fillerPublisherLogoIds[0],
    adSensePublisherId: '',
    adSenseSlotId: '',
    adManagerSlotId: '',
    adNetwork: 'none',
  };
}

function getCurrentUserState() {
  return {
    data: { id: 1, meta: { web_stories_tracking_optin: true } },
    isUpdating: false,
  };
}

function fetchSettings(currentState) {
  const settingsState = { ...currentState } || getSettingsState();
  settingsState.googleAnalyticsId = 'UA-000000-2';
  settingsState.publisherLogoIds = fillerPublisherLogoIds.slice(0, 2);
  return settingsState;
}

function updateSettings(updates, currentState) {
  const {
    googleAnalyticsId,
    publisherLogoIds,
    publisherLogoIdToRemove,
    publisherLogoToMakeDefault,
  } = updates;
  if (googleAnalyticsId !== undefined) {
    return {
      ...currentState,
      googleAnalyticsId,
    };
  } else if (publisherLogoIds) {
    return {
      ...currentState,
      publisherLogoIds: [
        ...new Set([...currentState.publisherLogoIds, ...publisherLogoIds]),
      ],
    };
  } else if (publisherLogoIdToRemove) {
    return {
      ...currentState,
      publisherLogoIds: currentState.publisherLogoIds.filter(
        (logoId) => logoId !== publisherLogoIdToRemove
      ),
    };
  } else if (publisherLogoToMakeDefault) {
    return {
      ...currentState,
      activePublisherLogoId: publisherLogoToMakeDefault,
    };
  }
  return currentState;
}

function getStoriesState() {
  const copiedStories = [...formattedStoriesArray];
  copiedStories.sort((a, b) => differenceInSeconds(b.modified, a.modified)); // initial sort is desc by modified
  return {
    ...defaultStoriesState,
    stories: copiedStories.reduce((acc, curr) => {
      acc[curr.id] = curr;

      return acc;
    }, {}),
    storiesOrderById: copiedStories.map(({ id }) => id),
    totalStoriesByStatus: getTotalStoriesByStatus(copiedStories),
    totalPages: 1,
  };
}

function clearStoryPreview(currentState) {
  return {
    ...currentState,
    previewMarkup: '',
  };
}

function createStoryPreview(currentState) {
  return {
    ...currentState,
    previewMarkup: '<p>I am markup for a preview</p>',
  };
}

function fetchStories(
  {
    status = STORY_STATUSES[0].value,
    searchTerm = '',
    sortOption = STORY_SORT_OPTIONS.LAST_MODIFIED,
    sortDirection,
  },
  currentState
) {
  const storiesState = { ...currentState } || getStoriesState();
  const statuses = status.split(',');

  storiesState.storiesOrderById = Object.values(storiesState.stories)
    .filter(
      ({ status: storyStatus, title }) =>
        statuses.includes(storyStatus) && title.includes(searchTerm)
    )
    .sort((a, b) => {
      let value;
      switch (sortOption) {
        case STORY_SORT_OPTIONS.DATE_CREATED: {
          value = new Date(a.created).getTime() - new Date(b.created).getTime();
          break;
        }
        case STORY_SORT_OPTIONS.LAST_MODIFIED: {
          value = differenceInSeconds(a[sortOption], b[sortOption]);
          break;
        }
        case STORY_SORT_OPTIONS.NAME: {
          value = a[sortOption].localeCompare(b[sortOption]);
          break;
        }
        case STORY_SORT_OPTIONS.CREATED_BY: {
          value = a.author.localeCompare(b.author);
          break;
        }
        default: {
          value = 0;
          break;
        }
      }

      const shouldSortDescending =
        (sortDirection && sortDirection === 'desc') ||
        (!sortDirection && sortOption === STORY_SORT_OPTIONS.LAST_MODIFIED);

      return shouldSortDescending ? value * -1 : value;
    })
    .map(({ id }) => id);
  return storiesState;
}

function updateStory(story, currentState) {
  const copy = { ...story };

  copy.title = copy.title.raw;
  copy.content = copy.content?.raw;
  copy.modified = new Date();
  return {
    ...currentState,
    stories: {
      ...currentState.stories,
      [copy.id]: copy,
    },
  };
}

function duplicateStory(story, currentState) {
  const copiedState = { ...currentState };
  const copiedStory = { ...story };

  // Update fields on copy
  copiedStory.id = Math.round(Math.random() * 1000);
  copiedStory.title = copiedStory.title + ' (Copy)';
  copiedStory.created = new Date().toISOString();
  copiedStory.modified = copiedStory.created;
  copiedStory.bottomTargetAction = copiedStory.bottomTargetAction.replace(
    story.id,
    copiedStory.id
  );
  copiedStory.editStoryLink = copiedStory.editStoryLink.replace(
    story.id,
    copiedStory.id
  );

  // insert into copiedState
  copiedState.stories[copiedStory.id] = copiedStory;
  copiedState.storiesOrderById = [
    copiedStory.id,
    ...copiedState.storiesOrderById,
  ];

  // update stories by status
  copiedState.totalStoriesByStatus = getTotalStoriesByStatus(
    Object.values(copiedState.stories)
  );

  return copiedState;
}

function trashStory(story, currentState) {
  const copiedState = { ...currentState };
  // delete story from state and filter from ordered list
  delete copiedState.stories[story.id];
  copiedState.storiesOrderById = copiedState.storiesOrderById.filter(
    (id) => id !== story.id
  );

  // update story status counts
  copiedState.totalStoriesByStatus = getTotalStoriesByStatus(
    Object.values(copiedState.stories)
  );

  return copiedState;
}

function getTotalStoriesByStatus(stories = []) {
  return stories.reduce(
    (acc, curr) => {
      if (acc[curr.status] > 0) {
        acc[curr.status] = acc[curr.status] + 1;
      } else {
        acc[curr.status] = 1;
      }

      return acc;
    },
    {
      all: stories.length,
      draft: 0,
      published: 0,
    }
  );
}

function getTemplatesState() {
  const copiedTemplates = [...formattedTemplatesArray];
  return {
    allPagesFetched: true,
    error: {},
    isLoading: false,
    savedTemplates: {},
    savedTemplatesOrderById: [],
    templates: copiedTemplates.reduce((acc, curr) => {
      acc[curr.id] = curr;

      return acc;
    }, {}),
    templatesOrderById: copiedTemplates.map(({ id }) => id),
    totalTemplates: copiedTemplates.length,
    totalPages: 1,
  };
}

function fetchExternalTemplates(currentState) {
  return currentState;
}

function fetchExternalTemplateById(id, currentState) {
  return Promise.resolve(currentState.templates?.[id] ?? {});
}

function toggleOptInTracking(currentUser) {
  return {
    ...currentUser,
    data: {
      ...currentUser.data,
      meta: {
        web_stories_tracking_optin: !currentUser.data.meta
          .web_stories_tracking_optin,
      },
    },
  };
}

function fetchRelatedTemplates(currentTemplateId, currentState) {
  if (!currentState.templates || !currentTemplateId) {
    return [];
  }

  return currentState.templatesOrderById
    .filter((id) => id !== currentTemplateId) // Filter out the current/active template
    .sort(() => 0.5 - Math.random()) // Randomly sort the array of ids
    .map((id) => currentState.templates[id]) // Map the ids to templates
    .slice(0, Math.floor(Math.random() * 5) + 1); // Return between 1 and 5 templates
}
