/**
 * This file is used to handle callbacks from the server.
 * It is used in the following apps:
 * - auth
 * - craft-unraid
 * - connect @todo
 * - connect-components
 */
import AES from 'crypto-js/aes';
import Utf8 from 'crypto-js/enc-utf8';
import { defineStore, createPinia, setActivePinia } from 'pinia';

export type SignIn = 'signIn';
export type SignOut = 'signOut';
export type OemSignOut = 'oemSignOut';
export type Troubleshoot = 'troubleshoot';
export type Recover = 'recover';
export type Replace = 'replace';
export type TrialExtend = 'trialExtend';
export type TrialStart = 'trialStart';
export type Purchase = 'purchase';
export type Redeem = 'redeem';
export type Renew = 'renew';
export type Upgrade = 'upgrade';
export type UpdateOs = 'updateOs';
export type DowngradeOs = 'downgradeOs';
export type Manage = 'manage';
export type MyKeys = 'myKeys';
export type LinkKey = 'linkKey';
export type Activate = 'activate';
export type AccountActionTypes = Troubleshoot | SignIn | SignOut | OemSignOut | Manage | MyKeys | LinkKey;
export type AccountKeyActionTypes = Recover | Replace | TrialExtend | TrialStart | UpdateOs | DowngradeOs;
export type PurchaseActionTypes = Purchase | Redeem | Renew | Upgrade | Activate;

export type ServerActionTypes = AccountActionTypes | AccountKeyActionTypes | PurchaseActionTypes;

export type ServerState = 'BASIC'
  | 'PLUS'
  | 'PRO'
  | 'TRIAL'
  | 'EEXPIRED'
  | 'ENOKEYFILE'
  | 'EGUID'
  | 'EGUID1'
  | 'ETRIAL'
  | 'ENOKEYFILE2'
  | 'ENOKEYFILE1'
  | 'ENOFLASH'
  | 'ENOFLASH1'
  | 'ENOFLASH2'
  | 'ENOFLASH3'
  | 'ENOFLASH4'
  | 'ENOFLASH5'
  | 'ENOFLASH6'
  | 'ENOFLASH7'
  | 'EBLACKLISTED'
  | 'EBLACKLISTED1'
  | 'EBLACKLISTED2'
  | 'ENOCONN'
  | 'STARTER'
  | 'UNLEASHED'
  | 'LIFETIME'
  | 'STALE'
  | undefined;

export interface ServerData {
  description?: string;
  deviceCount?: number;
  expireTime?: number;
  flashProduct?: string;
  flashVendor?: string;
  guid?: string;
  keyfile?: string;
  locale?: string;
  name?: string;
  osVersion?: string;
  osVersionBranch?: 'stable' | 'next' | 'preview' | 'test';
  registered: boolean;
  regExp?: number;
  regUpdatesExpired?: boolean;
  regGen?: number;
  regGuid?: string;
  regTy?: string;
  state: ServerState;
  wanFQDN?: string;
}

export interface UserInfo {
  'custom:ips_id'?: string;
  email?: string;
  email_verifed?: 'true' | 'false';
  preferred_username?: string;
  sub?: string;
  username?: string;
  /**
   * @param identities {string} JSON string containing @type Identity[]
   */
  identities?: string;
  /**
   * @param cognito:groups {string[]} JSON string containing @type string[]
   *
   * Will contain all groups for the signed in user, used for determining which branch to use
   * @example ["download-preview", "unraidPOOLID_Google"]
   */
  'cognito:groups'?: string[];
}

export interface ExternalSignIn {
  type: SignIn;
  apiKey: string;
  user: UserInfo;
}

export interface ExternalSignOut {
  type: SignOut | OemSignOut;
}

export interface ExternalKeyActions {
  type: PurchaseActionTypes | AccountKeyActionTypes;
  keyUrl: string;
}

export interface ExternalUpdateOsAction {
  type: DowngradeOs | UpdateOs;
  sha256: string;
}

export interface ServerPayload {
  type: ServerActionTypes;
  server: ServerData;
}

export interface ServerTroubleshoot {
  type: Troubleshoot;
  server: ServerData;
}

export type ExternalActions = ExternalSignIn | ExternalSignOut | ExternalKeyActions | ExternalUpdateOsAction;

export type UpcActions = ServerPayload | ServerTroubleshoot;

export type SendPayloads = ExternalActions[] | UpcActions[];

/**
 * Payload containing all actions that are sent from account.unraid.net to the server
 */
export interface ExternalPayload {
  type: 'forUpc';
  actions: ExternalActions[];
  sender: string;
}

/**
 * Payload containing all actions that are sent from a server to account.unraid.net
 */
export interface UpcPayload {
  actions: UpcActions[];
  sender: string;
  type: 'fromUpc';
}

export type QueryPayloads = ExternalPayload | UpcPayload;

export interface CallbackActionsStore {
  saveCallbackData: (decryptedData: QueryPayloads) => void;
  encryptionKey: string;
  sendType: 'fromUpc' | 'forUpc';
}

/**
 * @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
 * @see https://github.com/vuejs/pinia/discussions/1085
 */
setActivePinia(createPinia());

export const useCallbackStoreGeneric = (
  useCallbackActions: () => CallbackActionsStore
) =>
  defineStore('callback', () => {
    const callbackActions = useCallbackActions();

    const send = (url: string, payload: SendPayloads, redirectType?: 'newTab' | 'replace', sendType?: string) => {
      console.debug('[callback.send]');
      const stringifiedData = JSON.stringify({
        actions: [...payload],
        sender: window.location.href.replace('/Tools/Update', '/Tools'),
        type: sendType ?? callbackActions.sendType,
      });
      const encryptedMessage = AES.encrypt(
        stringifiedData,
        callbackActions.encryptionKey,
      ).toString();
      /**
       * Build and go to url
       */
      const destinationUrl = new URL(url.replace('/Tools/Update', '/Tools'));
      destinationUrl.searchParams.set('data', encodeURI(encryptedMessage));
      console.debug('[callback.send]', encryptedMessage, destinationUrl);
      if (redirectType === 'newTab') { // helpful when webgui is in an iframe and callbacks need to be opened in a new tab
        window.open(destinationUrl.toString(), '_blank');
        return;
      }
      if (redirectType === 'replace') { // helpful when autoredirecting and we want to replace the current url to prevent back button issues with auto redirect loops
        window.location.replace(destinationUrl.toString());
        return;
      }
      window.location.href = destinationUrl.toString();
    };

    const watcher = () => {
      console.debug('[callback.watcher]');
      const currentUrl = new URL(window.location.toString());
      const callbackValue = decodeURI(currentUrl.searchParams.get('data') ?? '');
      console.debug('[callback.watcher]', { callbackValue });
      if (!callbackValue) {
        return console.debug('[callback.watcher] no callback to handle');
      }

      const decryptedMessage = AES.decrypt(callbackValue, callbackActions.encryptionKey);
      const decryptedData: QueryPayloads = JSON.parse(decryptedMessage.toString(Utf8));
      console.debug('[callback.watcher]', decryptedMessage, decryptedData);
      // Parse the data and perform actions
      callbackActions.saveCallbackData(decryptedData);
    };

    return {
      send,
      watcher,
    };
  });
