import {EventFrom, sendParent} from 'xstate';
import {IssuersModel} from './IssuersModel';
import {IssuersActions} from './IssuersActions';
import {IssuersService} from './IssuersService';
import {IssuersGuards} from './IssuersGuards';
import {CredentialTypes} from '../VerifiableCredential/VCMetaMachine/vc';

const model = IssuersModel;

export const IssuerScreenTabEvents = model.events;
export const Issuer_Tab_Ref_Id = 'issuersMachine';

export const IssuersMachine = model.createMachine(
  {
    /** @xstate-layout [encoded data, 8322 characters] */
    predictableActionArguments: true,
    preserveActionOrder: true,
    id: Issuer_Tab_Ref_Id,
    context: model.initialContext,
    initial: 'displayIssuers',
    tsTypes: {} as import('./IssuersMachine.typegen').Typegen0,
    schema: {
      context: model.initialContext,
      events: {} as EventFrom<typeof model>,
    },
    states: {
      displayIssuers: {
        description: 'displays the issuers downloaded from the server',
        invoke: {
          src: 'downloadIssuersList',
          onDone: {
            actions: [
              'sendImpressionEvent',
              'setIssuers',
              'resetLoadingReason',
            ],
            target: 'selectingIssuer',
          },
          onError: {
            actions: ['setError'],
            target: 'error',
          },
        },
      },

      error: {
        description: 'reaches here when any error happens',
        on: {
          TRY_AGAIN: [
            {
              cond: 'shouldFetchIssuersAgain',
              actions: ['setLoadingReasonAsDisplayIssuers', 'resetError'],
              target: 'displayIssuers',
            },
            {
              cond: 'canSelectIssuerAgain',
              actions: 'resetError',
              target: 'selectingIssuer',
            },
            {
              cond: 'isCredentialOfferFlow',
              actions: ['resetError', 'resetCredentialOfferFlowType'],
              target: 'selectingIssuer',
            },
            {
              actions: ['setLoadingReasonAsSettingUp', 'resetError'],
              target: 'downloadIssuerWellknown',
            },
          ],
          RESET_ERROR: {
            actions: ['resetError', 'resetCredentialOfferFlowType'],
            target: 'selectingIssuer',
          },
        },
      },

      selectingIssuer: {
        description: 'waits for the user to select any issuer',
        on: {
          SCAN_CREDENTIAL_OFFER_QR_CODE: {
            target: 'waitingForQrScan',
          },
          DOWNLOAD_ID: {
            actions: sendParent('DOWNLOAD_ID'),
          },
          SELECTED_ISSUER: {
            actions: [
              model.assign({
                authEndpointToOpen: () => false,
              }),
              'setSelectedIssuerId',
              'setLoadingReasonAsSettingUp',
              'setSelectedIssuers',
            ],
            target: 'downloadIssuerWellknown',
          },
        },
      },
      waitingForQrScan: {
        description: 'waits for the user to scan the QR code',
        on: {
          QR_CODE_SCANNED: {
            actions: ['setLoadingReasonAsDownloadingCredentials', 'setQrData'],
            target: 'credentialDownloadFromOffer',
          },
          CANCEL: {
            actions: ['resetQrData', 'resetLoadingReason'],
            target: 'selectingIssuer',
          },
        },
      },

      credentialDownloadFromOffer: {
        entry: ['setCredentialOfferFlowType', 'resetSelectedIssuer'],
        invoke: {
          src: 'downloadCredentialFromOffer',
          onDone: {
            actions: [
              'setCredential',
              'setCredentialConfigurationId',
              model.assign({
                authEndpointToOpen: false,
              }),
            ],
            target: 'cachingCredentialOfferIssuerWellknown',
          },
          onError: [
            {
              actions: ['setError', 'resetLoadingReason'],
              target: 'error',
            },
          ],
        },
        on: {
          TOKEN_REQUEST: {
            actions: ['setTokenRequestObject'],
            target: '.tokenRequest',
          },
          PROOF_REQUEST: {
            actions: [
              'setCNonce',
              'setWellknwonKeyTypes',
              'setSelectedCredentialIssuer',
            ],
            target: '.keyManagement',
          },
          AUTH_ENDPOINT_RECEIVED: {
            actions: [
              model.assign({
                authEndpointToOpen: () => true,
                authEndpoint: (_, event) => event.authEndpoint,
              }),
            ],
          },
          TX_CODE_REQUEST: {
            actions: ['setRequestTxCode', 'setTxCodeDisplayDetails'],
            target: '.waitingForTxCode',
          },
          TRUST_ISSUER_CONSENT_REQUEST: {
            actions: ['setIssuerDisplayDetails', 'setSelectedCredentialIssuer'],
            target: '.checkingIssuerTrust',
          },
          CANCEL: {
            actions: ['resetLoadingReason'],
            target: '#issuersMachine.selectingIssuer',
          },
        },
        states: {
          idle: {},
          tokenRequest: {
            invoke: {
              src: 'sendTokenRequest',
              onDone: {
                actions: ['setTokenResponseObject'],
                target: 'sendTokenResponse',
              },
              onError: {
                actions: [
                  'setError',
                  'resetLoadingReason',
                  'resetRequestConsentToTrustIssuer',
                ],
                target: '#issuersMachine.error',
              },
            },
          },
          sendTokenResponse: {
            invoke: {
              src: 'sendTokenResponse',
              onDone: {
                target: '#issuersMachine.credentialDownloadFromOffer.idle',
              },
              onError: {
                actions: [
                  'setError',
                  'resetLoadingReason',
                  'sendDownloadingFailedToVcMeta',
                ],
                target: '#issuersMachine.error',
              },
            },
          },
          checkingIssuerTrust: {
            invoke: {
              src: 'checkIssuerIdInStoredTrustedIssuers',
              onDone: [
                {
                  cond: 'isIssuerIdInTrustedIssuers',
                  target: 'sendConsentGiven',
                },
                {
                  actions: ['setRequestConsentToTrustIssuer'],
                  target: 'credentialOfferDownloadConsent',
                },
              ],
            },
          },
          credentialOfferDownloadConsent: {
            description:
              'waits for the user to give consent to download the credential offer',
            on: {
              CANCEL: {
                actions: [
                  'resetQrData',
                  'resetLoadingReason',
                  'resetRequestConsentToTrustIssuer',
                ],
                target: 'sendConsentNotGiven',
              },
              ON_CONSENT_GIVEN: {
                actions: [
                  'setLoadingReasonAsDownloadingCredentials',
                  'resetRequestConsentToTrustIssuer',
                ],
                target: 'sendConsentGiven',
              },
            },
          },
          sendConsentNotGiven: {
            invoke: {
              src: 'sendConsentNotGiven',
              onDone: {
                target: '#issuersMachine.selectingIssuer',
              },
            },
          },
          sendConsentGiven: {
            invoke: {
              src: 'sendConsentGiven',
              onDone: {
                target: '.updatingTrustedIssuerList',
              },
              onError: {
                actions: [
                  'resetLoadingReason',
                  'setError',
                  'resetRequestConsentToTrustIssuer',
                ],
                target: '#issuersMachine.error',
              },
            },
            states: {
              updatingTrustedIssuerList: {
                invoke: {
                  src: 'checkIssuerIdInStoredTrustedIssuers',
                  onDone: [
                    {
                      cond: 'isIssuerIdInTrustedIssuers',
                      target:
                        '#issuersMachine.credentialDownloadFromOffer.idle',
                    },
                    {
                      target: 'addingIssuerToTrustedIssuers',
                    },
                  ],
                  onError: {
                    target: 'addingIssuerToTrustedIssuers',
                  },
                },
              },
              addingIssuerToTrustedIssuers: {
                invoke: {
                  src: 'addIssuerToTrustedIssuers',
                  onDone: {
                    target: '#issuersMachine.credentialDownloadFromOffer.idle',
                  },
                  onError: {
                    target: '#issuersMachine.credentialDownloadFromOffer.idle',
                  },
                },
              },
            },
          },
          waitingForTxCode: {
            on: {
              CANCEL: {
                actions: [
                  'resetLoadingReason',
                  'resetRequestTxCode',
                  'sendDownloadingFailedToVcMeta',
                ],
                target: '#issuersMachine.selectingIssuer',
              },
              TX_CODE_RECEIVED: {
                actions: ['setTxCode', 'resetRequestTxCode'],
                target: 'sendTxCode',
              },
            },
          },
          sendTxCode: {
            invoke: {
              src: 'sendTxCode',
              onDone: {
                target: '#issuersMachine.credentialDownloadFromOffer.idle',
              },
              onError: {
                actions: [
                  'resetLoadingReason',
                  'resetRequestTxCode',
                  'setError',
                  'sendDownloadingFailedToVcMeta',
                ],
                target: '#issuersMachine.error',
              },
            },
          },
          keyManagement: {
            initial: 'setSelectedKey',
            states: {
              setSelectedKey: {
                invoke: {
                  src: 'getKeyOrderList',
                  onDone: {
                    actions: ['setSelectedKey'],
                    target: 'getKeyPairFromKeystore',
                  },
                  onError: {
                    actions: [
                      'setError',
                      'resetLoadingReason',
                      'sendDownloadingFailedToVcMeta',
                    ],
                    target: '#issuersMachine.error',
                  },
                },
              },
              getKeyPairFromKeystore: {
                invoke: {
                  src: 'getKeyPair',
                  onDone: {
                    actions: ['loadKeyPair'],
                    target: 'constructProof',
                  },
                  onError: [
                    {
                      cond: 'hasUserCancelledBiometric',
                      target: 'userCancelledBiometric',
                    },
                    {
                      cond: 'isKeyTypeNotFound',
                      actions: [
                        'setError',
                        'resetLoadingReason',
                        'sendDownloadingFailedToVcMeta',
                      ],
                      target: '#issuersMachine.error',
                    },
                    {
                      target: 'generateKeyPair',
                    },
                  ],
                },
              },
              userCancelledBiometric: {
                on: {
                  TRY_AGAIN: {
                    target: 'getKeyPairFromKeystore',
                  },
                  RESET_ERROR: {
                    actions: 'resetLoadingReason',
                    target: '#issuersMachine.selectingIssuer',
                  },
                },
              },
              generateKeyPair: {
                invoke: {
                  src: 'generateKeyPair',
                  onDone: {
                    actions: [
                      'setPublicKey',
                      'setPrivateKey',
                      'setLoadingReasonAsDownloadingCredentials',
                      'storeKeyPair',
                    ],
                    target: 'constructProof',
                  },
                },
              },
              constructProof: {
                invoke: {
                  src: 'constructProof',
                  onDone: {
                    target: '#issuersMachine.credentialDownloadFromOffer.idle',
                  },
                  onError: {
                    actions: [
                      'setError',
                      'resetLoadingReason',
                      'sendDownloadingFailedToVcMeta',
                    ],
                    target: '#issuersMachine.error',
                  },
                },
              },
            },
          },
        },
      },
      cachingCredentialOfferIssuerWellknown: {
        invoke: {
          src: 'cacheIssuerWellknown',
          onDone: {
            actions: [
              'setCredentialOfferIssuer',
              'setCredentialOfferIssuerWellknownResponse',
              'setCredentialOfferCredentialType',
            ],
            target: 'proccessingCredential',
          },
          onError: {
            actions: ['resetLoadingReason'],
            target: 'error',
          },
        },
      },
      proccessingCredential: {
        invoke: {
          src: 'updateCredential',
          onDone: {
            actions: [
              'setVerifiableCredential',
              'setCredentialWrapper',
            ],
            target: 'verifyingCredential',
          },
        },
      },
      downloadIssuerWellknown: {
        invoke: {
          src: 'downloadIssuerWellknown',
          onDone: {
            actions: ['updateIssuerFromWellknown'],
            target: 'getCredentialTypes',
          },
          onError: {
            actions: ['setError', 'resetLoadingReason'],
            target: 'error',
          },
        },
      },

      getCredentialTypes: {
        description: 'fetches the supported credential types from the issuer',
        invoke: {
          src: 'getCredentialTypes',
          onDone: {
            actions: 'setSupportedCredentialTypes',
            target: 'selectingCredentialType',
          },
          onError: {
            actions: [
              'setCredentialTypeListDownloadFailureError',
              'resetLoadingReason',
            ],
            target: 'error',
          },
        },
      },

      selectingCredentialType: {
        description: 'waits for the user to select a credential type',
        entry: model.assign({
          authEndpointToOpen: () => false,
        }),
        on: {
          CANCEL: {
            target: 'displayIssuers',
          },
          SELECTED_CREDENTIAL_TYPE: {
            actions: 'setSelectedCredentialType',
            target: 'downloadCredentials',
          },
        },
      },

      downloadCredentials: {
        entry: ['setLoadingReasonAsDownloadingCredentials'],
        invoke: {
          src: 'downloadCredential',
          onDone: {
            actions: [
              'setVerifiableCredential',
              'setCredentialWrapper',
              model.assign({
                authEndpointToOpen: false,
              }),
            ],
            target: 'verifyingCredential',
          },
          onError: [
            {
              cond: 'hasUserCancelledBiometric',
              target: '.userCancelledBiometric',
            },
            {
              actions: ['setError', 'resetLoadingReason'],
              target: 'error',
            },
          ],
        },
        on: {
          AUTH_ENDPOINT_RECEIVED: {
            actions: [
              model.assign({
                authEndpointToOpen: () => true,
                authEndpoint: (_, event) => event.authEndpoint,
              }),
            ],
          },
          TOKEN_REQUEST: {
            actions: ['setTokenRequestObject'],
            target: '.tokenRequest',
          },
          PROOF_REQUEST: {
            actions: ['setCNonce', 'setWellknwonKeyTypes'],
            target: '.keyManagement',
          },
          CANCEL: {
            target: 'selectingIssuer',
            actions: ['resetSelectedCredentialType', 'resetLoadingReason'],
          },
        },
        initial: 'idle',
        states: {
          idle: {},
          tokenRequest: {
            invoke: {
              src: 'sendTokenRequest',
              onDone: {
                actions: ['setTokenResponseObject', 'setAccessToken'],
                target: 'sendTokenResponse',
              },
              onError: {
                actions: [
                  'setError',
                  (_context, event, _meta) =>
                    console.error(
                      'Error sending token request in downloadCredentials',
                      event,
                    ),
                  'resetLoadingReason',
                ],
                target: '#issuersMachine.error',
              },
            },
          },
          sendTokenResponse: {
            invoke: {
              src: 'sendTokenResponse',
              onDone: {
                target: '#issuersMachine.downloadCredentials.idle',
              },
              onError: {
                actions: [
                  'setError',
                  (_context, event, _meta) =>
                    console.error(
                      'Error sending token response in downloadCredentials',
                      event,
                    ),
                  'resetLoadingReason',
                ],
                target: '#issuersMachine.error',
              },
            },
          },
          constructProof: {
            invoke: {
              src: 'constructAndSendProofForTrustedIssuers',
              onDone: {
                target: '#issuersMachine.downloadCredentials.idle',
              },
              onError: [
                {
                  cond: 'hasUserCancelledBiometric',
                  target: 'userCancelledBiometric',
                },
                {
                  actions: [
                    'setError',
                    'resetLoadingReason',
                    'sendDownloadingFailedToVcMeta',
                  ],
                  target: '#issuersMachine.error',
                },
              ],
            },
          },
          userCancelledBiometric: {
            on: {
              TRY_AGAIN: {
                target: 'constructProof',
              },
              RESET_ERROR: {
                actions: 'resetLoadingReason',
                target: '#issuersMachine.selectingIssuer',
              },
            },
          },
          keyManagement: {
            initial: 'setSelectedKey',
            states: {
              setSelectedKey: {
                invoke: {
                  src: 'getKeyOrderList',
                  onDone: {
                    actions: ['setSelectedKey'],
                    target: 'getKeyPairFromKeystore',
                  },
                  onError: {
                    actions: [
                      'setError',
                      'resetLoadingReason',
                      'sendDownloadingFailedToVcMeta',
                    ],
                    target: '#issuersMachine.error',
                  },
                },
              },
              getKeyPairFromKeystore: {
                invoke: {
                  src: 'getKeyPair',
                  onDone: {
                    actions: ['loadKeyPair'],
                    target:
                      '#issuersMachine.downloadCredentials.constructProof',
                  },
                  onError: [
                    {
                      cond: 'hasUserCancelledBiometric',
                      target: 'userCancelledBiometric',
                    },
                    {
                      cond: 'isKeyTypeNotFound',
                      actions: [
                        'setError',
                        'resetLoadingReason',
                        'sendDownloadingFailedToVcMeta',
                      ],
                      target: '#issuersMachine.error',
                    },
                    {
                      target: 'generateKeyPair',
                    },
                  ],
                },
              },
              userCancelledBiometric: {
                on: {
                  TRY_AGAIN: {
                    target: 'getKeyPairFromKeystore',
                  },
                  RESET_ERROR: {
                    actions: 'resetLoadingReason',
                    target: '#issuersMachine.selectingIssuer',
                  },
                },
              },
              generateKeyPair: {
                invoke: {
                  src: 'generateKeyPair',
                  onDone: {
                    actions: [
                      'setPublicKey',
                      'setPrivateKey',
                      'setLoadingReasonAsDownloadingCredentials',
                      'storeKeyPair',
                    ],
                    target:
                      '#issuersMachine.downloadCredentials.constructProof',
                  },
                  onError: {
                    actions: [
                      'setError',
                      'resetLoadingReason',
                      'sendDownloadingFailedToVcMeta',
                    ],
                    target: '#issuersMachine.error',
                  },
                },
              },
            },
          },
        },
      },

      verifyingCredential: {
        invoke: {
          src: 'verifyCredential',
          onDone: {
            actions: ['sendSuccessEndEvent', 'setVerificationResult','resetCredentialOfferFlowType',],
            target: 'storing',
          },
          onError: [
            {
              cond: 'isVerificationPendingBecauseOfNetworkIssue',
              actions: ['resetLoadingReason', 'resetVerificationResult'],
              target: 'storing',
            },
            {
              actions: [
                'resetLoadingReason',
                'sendErrorEndEvent',
                'updateVerificationErrorMessage',
              ],
              target: 'handleVCVerificationFailure',
            },
          ],
        },
      },

      handleVCVerificationFailure: {
        on: {
          RESET_VERIFY_ERROR: {
            actions: ['resetVerificationErrorMessage'],
            target: 'selectingIssuer',
          },
        },
      },

      storing: {
        entry: [
          'setVCMetadata',
          'setMetadataInCredentialData',
          'storeVerifiableCredentialMeta',
          'storeVerifiableCredentialData',
          'storeVcsContext',
          'storeVcMetaContext',
          'logDownloaded',
        ],
        invoke: {
          src: 'isUserSignedAlready',
          onDone: {
            cond: 'isSignedIn',
            actions: ['sendBackupEvent'],
            target: 'done',
          },
        },
      },

      idle: {
        on: {
          COMPLETED: {
            target: 'done',
          },
          CANCEL: {
            target: 'selectingIssuer',
          },
        },
      },

      done: {
        type: 'final',
      },
    },
  },
  {
    actions: IssuersActions(model),
    services: IssuersService(),
    guards: IssuersGuards(),
  },
);

// --- Interfaces ---

export interface logoType {
  url: string;
  alt_text: string;
}

export interface displayType {
  name: string;
  locale: string;
  language: string;
  logo: logoType;
  background_color: string;
  background_image: {uri: string};
  text_color: string;
  title: string;
  description: string;
}

export interface issuerType {
  issuer_id: string;
  credential_issuer: string;
  protocol: string;
  client_id: string;
  redirect_uri: string;
  token_endpoint: string;
  credential_endpoint: string;
  credential_configurations_supported: object;
  display: [displayType];
  credentialTypes: [CredentialTypes];
  authorizationEndpoint: string;
  credential_issuer_host: string;
  authorization_servers: [string];
}
