import { useState, useRef, useContext } from 'react';
import { StoreContext } from 'context/StoreContext';
import { UploadContext } from 'context/UploadContext';
import { addFlag } from 'api/addFlag';
import { getCard } from 'api/getCard';
import { getCards } from 'api/getCards';
import { getCardProfile } from 'api/getCardProfile';
import { setCardProfile } from 'api/setCardProfile';
import { getCardDetail } from 'api/getCardDetail';
import { getContactProfile } from 'api/getContactProfile';
import { getContactChannels } from 'api/getContactChannels';
import { getContactChannelDetail } from 'api/getContactChannelDetail';
import { getContactChannelSummary } from 'api/getContactChannelSummary';
import { getCardImageUrl } from 'api/getCardImageUrl';
import { addCard } from 'api/addCard';
import { removeCard } from 'api/removeCard';
import { setCardConnecting, setCardConnected, setCardConfirmed } from 'api/setCardStatus';
import { getCardOpenMessage } from 'api/getCardOpenMessage';
import { setCardOpenMessage } from 'api/setCardOpenMessage';
import { getCardCloseMessage } from 'api/getCardCloseMessage';
import { setCardCloseMessage } from 'api/setCardCloseMessage';
import { getContactChannelTopic } from 'api/getContactChannelTopic';
import { getContactChannelTopics } from 'api/getContactChannelTopics';
import { getContactChannelTopicAssetUrl } from 'api/getContactChannelTopicAssetUrl';
import { addContactChannelTopic } from 'api/addContactChannelTopic';
import { setContactChannelTopicSubject } from 'api/setContactChannelTopicSubject';
import { removeContactChannel } from 'api/removeContactChannel';
import { removeContactChannelTopic } from 'api/removeContactChannelTopic';
import { getContactChannelNotifications } from 'api/getContactChannelNotifications';
import { setContactChannelNotifications } from 'api/setContactChannelNotifications';

export function useCardContext() {
  const [state, setState] = useState({
    offsync: false,
    cards: new Map(),
    viewRevision: null,
  });
  const upload = useContext(UploadContext);
  const access = useRef(null);
  const setRevision = useRef(null);
  const curRevision = useRef(null);
  const cards = useRef(new Map());
  const syncing = useRef(false);
  const store = useContext(StoreContext);

  const updateState = (value) => {
    setState((s) => ({ ...s, ...value }))
  }

  const setCardItem = (card) => {
    return {
      cardId: card.id,
      revision: card.revision,
      detailRevision: card.data?.detailRevision,
      profileRevision: card.data?.profileRevision,
      detail: card.data?.cardDetail,
      profile: card.data?.cardProfile,
      notifiedView: card.data?.notifiedView,
      notifiedProfile: card.data?.notifiedProfile,
      notifiedArticle: card.data?.notifiedArticle,
      notifiedChannel: card.data?.notifiedChannel,
    }
  }

  const setCardField = (cardId, field, value) => {
    const item = cards.current.get(cardId);
    if (item?.card) {
      item.card[field] = value;
      cards.current.set(cardId, { ...item });
      updateState({ cards: cards.current });
    }
  }

  const setCardChannelItem = (cardChannel) => {
    return {
      channelId: cardChannel.id,
      revision: cardChannel.revision,
      detailRevision: cardChannel.data.detailRevision,
      topicRevision: cardChannel.data.topicRevision,
      detail: cardChannel.data.channelDetail,
      summary: cardChannel.data.channelSummary,
    };
  };

  const setCardChannelField = (cardId, channelId, field, value, field2, value2) => {
    const card = cards.current.get(cardId);
    if (card) {
      const channel = card.channels.get(channelId);
      if (channel) {
        channel[field] = value;
        if(field2) {
          channel[field2] = value2;
        }
        card.channels.set(channelId, { ...channel });
        cards.current.set(cardId, { ...card });
        updateState({ cards: cards.current });
      }
    }
  };
  
  const resyncCard = async (cardId) => {
    if (!syncing.current) {
      syncing.current = true;

      try {
        const { server, token, guid } = access.current || {};
        const entry = cards.current.get(cardId);
        if (entry?.card?.detail.status === 'connected') {
          const card = await getCard(server, token, cardId);
          const { notifiedView, notifiedProfile, notifiedArticle, notifiedChannel } = card.data || {};
          const cardRevision = { view: notifiedView, profile: notifiedProfile, artcile: notifiedArticle, channel: notifiedChannel };
          await syncCard(server, token, guid, entry, cardRevision);
          await store.actions.clearCardItemOffsync(guid, cardId);
          entry.card.offsync = false;
        
          cards.current.set(cardId, entry);
          updateState({ cards: cards.current });
        }
      }
      catch(err) {
        console.log(err);
      }
      syncing.current = false;
      await sync();
    }
  }

  const sync = async () => {
    if (access.current && !syncing.current && setRevision.current !== curRevision.current) {
      syncing.current = true;
      try {
        const { server, token, guid } = access.current || {};
        const revision = curRevision.current;
        const delta = await getCards(server, token, setRevision.current);
        for (let card of delta) {
          if (card.data) {
            const item = setCardItem(card);
            const entry = cards.current.get(card.id) || { channels: new Map() };
            if (!entry.card) {
              const { cardId, detailRevision, profileRevision } = item;
              const entryCard = { cardId, detailRevision, profileRevision };
              if (item.detail) {
                entryCard.detail = item.detail;
              }
              else {
                entryCard.detail = await getCardDetail(server, token, card.id);
              }
              if (item.profile) {
                entryCard.profile = item.profile;
              }
              else {
                entryCard.profile = await getCardProfile(server, token, card.id);
              }
              await store.actions.setCardItem(guid, entryCard);
              entry.card = entryCard;
              cards.current.set(card.id, entry);
            }
            else {
              const { profileRevision, detailRevision } = entry.card; 
              if (item.profileRevision !== profileRevision) {
                if (item.profile) {
                  entry.card.profile = item.profile;
                }
                else {
                  entry.card.profile = await getCardProfile(server, token, card.id);
                }
                entry.card.profileRevision = item.profileRevision;
                await store.actions.setCardItemProfile(guid, card.id, entry.card.profileRevision, entry.card.profile);
              }
              if (item.detailRevision !== detailRevision) {
                if (item.detail) {
                  entry.card.detail = item.detail;
                }
                else {
                  entry.card.detail = await getCardDetail(server, token, card.id);
                }
                entry.card.detailRevision = item.detailRevision;
                await store.actions.setCardItemDetail(guid, card.id, entry.card.detailRevision, entry.card.detail);
              }
            }
            if (entry.card.detail?.status === 'connected' && !entry.card.offsync) {
              try {
                const { notifiedView, notifiedProfile, notifiedArticle, notifiedChannel } = item;
                const cardRevision = { view: notifiedView, profile: notifiedProfile, article: notifiedArticle, channel: notifiedChannel };
                await syncCard(server, token, guid, entry, cardRevision);
              }
              catch (err) {
                console.log(err);
                entry.card.offsync = true;
                await store.actions.setCardItemOffsync(guid, card.id);
              }
            }
            cards.current.set(card.id, { ...entry }); 
          }
          else {
            const entry = cards.current.get(card.id) || { card: {}, channels: new Map() };
            const ids = [];
            entry.channels.forEach((value, key) => {
              ids.push(key);
            });
            for (let i = 0; i < ids.length; i++) {
              await store.actions.clearCardChannelTopicItems(guid, card.id, ids[i]);
            }
            await store.actions.clearCardChannelItems(guid, card.id); 
            await store.actions.clearCardItem(guid, card.id);
            cards.current.delete(card.id);
          }
        }
        setRevision.current = revision;
        await store.actions.setCardRevision(guid, revision);
        updateState({ offsync: false, cards: cards.current });
      }
      catch (err) {
        console.log(err);
        syncing.current = false;
        updateState({ offsync: true });
        return;
      }

      syncing.current = false;
      await sync();
    }
  };

  const syncCard = async (server, token, guid, entry, cardRevision) => {

    const { detail, profile, cardId } = entry.card;
    const { notifiedView, notifiedProfile, notifiedArticle, notifiedChannel } = entry.card;
    const cardServer = profile?.node ? profile.node : server;
    const cardToken = `${profile?.guid}.${detail?.token}`;

    if (entry.card.notifiedProfile !== cardRevision.profile) {
      if (entry.card.profileRevision !== cardRevision.profile) {
        const message = await getContactProfile(cardServer, cardToken);
        await setCardProfile(server, token, cardId, message);
      }
      entry.card.notifiedProfile = cardRevision.profile;
      store.actions.setCardItemNotifiedProfile(guid, cardId, cardRevision.profile);
    }

    if (entry.card.notifiedView !== cardRevision.view || entry.card.notifiedChannel !== cardRevision.channel) {
      const view = cardRevision.view === entry.card.notifiedView ? entry.card.notifiedView : null;
      const channel = cardRevision.view === entry.card.notifiedView ? entry.card.notifiedChannel : null;
      const delta = await getContactChannels(cardServer, cardToken, view, channel);
      for (let channel of delta) {
        if (channel.data) {
          const channelItem = setCardChannelItem(channel);
          const channelEntry = entry.channels.get(channel.id);
          if (!channelEntry) {
            if (!channelItem.detail) {
              channelItem.detail = await getContactChannelDetail(cardServer, cardToken, channel.id);
            }
            if (!channelItem.summary) {
              channelItem.summary = await getContactChannelSummary(cardServer, cardToken, channel.id);
            }
            await store.actions.setCardChannelItem(guid, cardId, channelItem);
            entry.channels.set(channel.id, { ...channelItem });
          }
          else {
            if (channelItem.detailRevision !== channelEntry.detailRevision) {
              if (channelItem.detail) {
                channelEntry.detail = channelItem.detail;
              }
              else {
                channelEntry.detail = await getContactChannelDetail(cardServer, cardToken, channel.id);
              }
              channelEntry.unsealedDetail = null;
              channelEntry.detailRevision = channelItem.detailRevision;
              await store.actions.setCardChannelItemDetail(guid, cardId, channel.id, channelEntry.detailRevision, channelEntry.detail);
            }
            if (channelItem.topicRevision !== channelEntry.topicRevision) {
              if (channelItem.summary) {
                channelEntry.summary = channelItem.summary;
              }
              else {
                channelEntry.summary = await getContactChannelSummary(cardServer, cardToken, channel.id);
              }
              channelEntry.unsealedSummary = null;
              channelEntry.topicRevision = channelItem.topicRevision;
              await store.actions.setCardChannelItemSummary(guid, cardId, channel.id, channelEntry.topicRevision, channelEntry.summary);
            }
            entry.channels.set(channel.id, { ...channelEntry });
          }
        }
        else {
          await store.actions.clearCardChannelTopicItems(guid, cardId, channel.id);
          await store.actions.clearCardChannelItem(guid, cardId, channel.id);
          entry.channels.delete(channel.id);
        }
      }
      entry.card.notifiedChannel = cardRevision.channel;
      await store.actions.setCardItemNotifiedChannel(guid, cardId, cardRevision.channel);
      entry.card.notifiedView = cardRevision.view;
      await store.actions.setCardItemNotifiedView(guid, cardId, cardRevision.view);
    }
  };

  const actions = {
    setSession: async (session) => {
      if (access.current || syncing.current) {
        throw new Error('invalid card state');
      }
      access.current = session;
      cards.current = new Map();
      const cardItems = await store.actions.getCardItems(session.guid);
      for(card of cardItems) {
        const entry = { card, channels: new Map() };
        const cardChannelItems = await store.actions.getCardChannelItems(session.guid, card.cardId);
        for (cardChannel of cardChannelItems) {
          entry.channels.set(cardChannel.channelId, cardChannel);
        }
        cards.current.set(card.cardId, entry);
      }
      const status = await store.actions.getCardRequestStatus(session.guid);
      const revision = await store.actions.getCardRevision(session.guid);
      curRevision.current = revision;
      setRevision.current = revision;
      setState({ offsync: false, viewRevision: status?.revision, cards: cards.current });
    },
    clearSession: () => {
      access.current = null;
    },
    setRevision: (revision) => {
      curRevision.current = revision;
      sync();
    },
    addCard: async (message) => {
      const { server, token } = access.current || {};
      return await addCard(server, token, message);
    },
    removeCard: async (cardId) => {
      const { server, token } = access.current || {};
      return await removeCard(server, token, cardId);
    },
    setCardConnecting: async (cardId) => {
      const { server, token } = access.current || {};
      return await setCardConnecting(server, token, cardId);
    },
    setCardConnected: async (cardId, cardToken, revision) => {
      const { server, token } = access.current || {};
      return await setCardConnected(server, token, cardId, cardToken,
          revision.viewRevision, revision.articleRevision,
          revision.channelRevision, revision.profileRevision);
    },
    setCardConfirmed: async (cardId) => {
      const { server, token } = access.current || {};
      return await setCardConfirmed(server, token, cardId);
    },
    getCardOpenMessage: async (cardId) => {
      const { server, token } = access.current || {};
      return await getCardOpenMessage(server, token, cardId);
    },
    setCardOpenMessage: async (server, message) => {
      return await setCardOpenMessage(server, message);
    },
    getCardCloseMessage: async (cardId) => {
      const { server, token } = access.current || {};
      return await getCardCloseMessage(server, token, cardId);
    },
    setCardCloseMessage: async (server, message) => {
      return await setCardCloseMessage(server, message);
    },
    getCardImageUrl: (cardId) => {
      const { profileRevision } = cards.current.get(cardId)?.card || {};
      const { server, token } = access.current || {};
      return getCardImageUrl(server, token, cardId, profileRevision);
    },
    removeChannel: async (cardId, channelId) => {
      const { detail, profile } = cards.current.get(cardId)?.card || {};
      const cardToken = `${profile?.guid}.${detail?.token}`;
      return await removeContactChannel(profile?.node, cardToken, channelId);
    },
    addTopic: async (cardId, channelId, type, message, files) => {
      const { detail, profile } = cards.current.get(cardId)?.card || {};
      const cardToken = `${profile?.guid}.${detail?.token}`;
      const node = profile?.node ? profile.node : access.current?.server;
      if (files?.length > 0) {
        const topicId = await addContactChannelTopic(node, cardToken, channelId, null, null, null);
        upload.actions.addTopic(node, cardToken, channelId, topicId, files, async (assets) => {
          const subject = message(assets);
          await setContactChannelTopicSubject(node, cardToken, channelId, topicId, type, subject);
        }, async () => {
          try {
            await removeContactChannelTopic(node, cardToken, channelId, topicId);
          }
          catch (err) {
            console.log(err);
          }
        }, cardId);
      }
      else {
        const subject = message([]);
        await addContactChannelTopic(node, cardToken, channelId, type, subject, []);
      }
    },
    removeTopic: async (cardId, channelId, topicId) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const cardToken = `${profile?.guid}.${detail?.token}`;
      const node = profile?.node ? profile.node : access.current.server;
      return await removeContactChannelTopic(node, cardToken, channelId, topicId);
    },
    setTopicSubject: async (cardId, channelId, topicId, type, subject) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const cardToken = `${profile?.guid}.${detail?.token}`;
      const node = profile?.node ? profile.node : access.current.server;
      return await setContactChannelTopicSubject(node, cardToken, channelId, topicId, type, subject);
    },
    getTopicAssetUrl: (cardId, channelId, topicId, assetId) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const cardToken = `${profile?.guid}.${detail?.token}`;
      const node = profile?.node ? profile.node : access.current.server;
      return getContactChannelTopicAssetUrl(node, cardToken, channelId, topicId, assetId);
    },
    getTopics: async (cardId, channelId, revision, count, begin, end) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const cardToken = `${profile?.guid}.${detail?.token}`;
      const node = profile?.node ? profile.node : access.current.server;
      return await getContactChannelTopics(node, cardToken, channelId, revision, count, begin, end);
    },
    getTopic: async (cardId, channelId, topicId) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const cardToken = `${profile?.guid}.${detail?.token}`;
      const node = profile?.node ? profile.node : access.current.server;
      return await getContactChannelTopic(node, cardToken, channelId, topicId);
    },
    setContactRevision: async (cardId, revision) => {
      const { guid } = access.current || {};
      await store.actions.setCardRequestStatus(guid, { revision });
      updateState({ viewRevision: revision });
    },
    setChannelReadRevision: async (cardId, channelId, revision) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelItemReadRevision(guid, cardId, channelId, revision);
      setCardChannelField(cardId, channelId, 'readRevision', revision);
    },
    setChannelSyncRevision: async (cardId, channelId, revision) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelItemSyncRevision(guid, cardId, channelId, revision);
      setCardChannelField(cardId, channelId, 'syncRevision', revision);
    },
    setChannelTopicMarker: async (cardId, channelId, marker) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelItemTopicMarker(guid, cardId, channelId, marker);
      setCardChannelField(cardId, channelId, 'topicMarker', marker);
    },
    setChannelMarkerAndSync: async (cardId, channelId, marker, revision) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelItemMarkerAndSync(guid, cardId, channelId, marker, revision);
      setCardChannelField(cardId, channelId, 'topicMarker', marker, 'syncRevision', revision);
    },
    setCardFlag: async (cardId) => {
      const { guid } = access.current || {};
      await store.actions.setCardItemBlocked(guid, cardId);
      setCardField(cardId, 'blocked', true);
    },
    clearCardFlag: async (cardId) => {
      const { guid } = access.current || {};
      await store.actions.clearCardItemBlocked(guid, cardId);
      setCardField(cardId, 'blocked', false);
    },
    setChannelFlag: async (cardId, channelId) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelItemBlocked(guid, cardId, channelId);
      setCardChannelField(cardId, channelId, 'blocked', true);
    },
    clearChannelFlag: async (cardId, channelId) => {
      const { guid } = access.current || {};
      await store.actions.clearCardChannelItemBlocked(guid, cardId, channelId);
      setCardChannelField(cardId, channelId, 'blocked', false);
    },
    setTopicFlag: async (cardId, channelId, topicId) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelTopicBlocked(guid, cardId, channelId, topicId, true);
    },
    clearTopicFlag: async (cardId, channelId, topicId) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelTopicBlocked(guid, cardId, channelId, topicId, false);
    },
    getFlaggedTopics: async () => {
      const { guid } = access.current || {};
      return await store.actions.getCardChannelTopicBlocked(guid);
    },
    addChannelAlert: async (cardId, channelId) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const node = profile?.node ? profile.node : access.current.server;
      return await addFlag(node, profile?.guid, channelId);
    },
    addTopicAlert: async (cardId, channelId, topicId) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const node = profile?.node ? profile.node : access.current.server;
      return await addFlag(node, profile?.guid, channelId, topicId);
    },
    getChannelNotifications: async (cardId, channelId) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const token = `${profile?.guid}.${detail?.token}`;
      const node = profile?.node ? profile.node : access.current.server;
      return await getContactChannelNotifications(node, token, channelId);
    },
    setChannelNotifications: async (cardId, channelId, notify) => {
      const { detail, profile } = (cards.current.get(cardId) || {}).card;
      const token = `${profile?.guid}.${detail?.token}`;
      const node = profile?.node ? profile.node : access.current.server;
      return await setContactChannelNotifications(node, token, channelId, notify);
    },
    getTopicItems: async (cardId, channelId) => {
      const { guid } = access.current || {};
      return await store.actions.getCardChannelTopicItems(guid, cardId, channelId);
    },
    getTopicItemsId: async (cardId, channelId) => {
      const { guid } = access.current || {};
      return await store.actions.getCardChannelTopicItemsId(guid, cardId, channelId);
    },
    getTopicItemsById: async (cardId, channelId, topics) => {
      const { guid } = access.current || {};
      return await store.actions.getCardChannelTopicItemsById(guid, cardId, channelId, topics);
    },
    setTopicItem: async (cardId, channelId, topicId, topic) => {
      const { guid } = access.current || {};
      return await store.actions.setCardChannelTopicItem(guid, cardId, channelId, topicId, topic);
    },
    clearTopicItem: async (cardId, channelId, topicId) => {
      const { guid } = access.current || {};
      return await store.actions.clearCardChannelTopicItem(guid, cardId, channelId, topicId);
    },
    clearTopicItems: async (cardId, channelId) => {
      const { guid } = access.current || {};
      return await store.actions.clearCardChannelTopicItems(guid, cardId, channelId);
    },
    setUnsealedChannelSubject: async (cardId, channelId, revision, unsealed) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelItemUnsealedDetail(guid, cardId, channelId, revision, unsealed);
      setCardChannelField(cardId, channelId, 'unsealedDetail', unsealed);
    },
    setUnsealedChannelSummary: async (cardId, channelId, revision, unsealed) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelItemUnsealedSummary(guid, cardId, channelId, revision, unsealed);
      setCardChannelField(cardId, channelId, 'unsealedSummary', unsealed);
    },
    setUnsealedTopicSubject: async (cardId, channelId, topicId, revision, unsealed) => {
      const { guid } = access.current || {};
      await store.actions.setCardChannelTopicItemUnsealedDetail(guid, cardId, channelId, topicId, revision, unsealed);
    },    
    resync: async () => {
      await sync();
    },
    resyncCard: async (cardId) => {
      await resyncCard(cardId);
    },
  }
  
  return { state, actions }
}

