<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Settings</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content class="ion-padding">
  <ion-accordion-group :value="defaultAccordionOpen" v-if="!loading">
  <ion-accordion value="1">
    <ion-item slot="header" color="light">
      <ion-label>Security</ion-label>
    </ion-item>
    <div class="ion-padding" slot="content">
      <ion-list>
      <ion-item v-if="noAccounts">You need at least one account to touch this settings</ion-item>
      <ion-list :disabled="noAccounts">
            <ion-item>
              <ion-label>Enable Storage Encryption</ion-label>
              <ion-toggle :key="updateKey" @ion-change="changeEncryption" slot="end" :checked="settings.s.enableStorageEnctyption"></ion-toggle>
            </ion-item>
            <ion-item>
              This will require to input an encrypto key when storage is locked.
            </ion-item>
          </ion-list>
          <ion-item :disabled="!settings.s.enableStorageEnctyption">
            <ion-label>Enable Auto Lock</ion-label>
            <ion-toggle :key="updateKey" @ion-change="changeAutoLock" slot="end" :checked="settings.s.lockOutEnabled"></ion-toggle>
          </ion-item>
          <ion-list>
          <ion-item :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
            <ion-label>Auto-lock Period: (2-120) minutes</ion-label>
          </ion-item> 
          <ion-item  :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
          <ion-input :key="updateKey"  v-model="settings.s.lockOutPeriod" type="number"></ion-input>
        </ion-item>
        <ion-item  :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
          <ion-button @click="setTime">Set Auto-lock</ion-button>
      </ion-item>
      </ion-list>
          <ion-list>
            <ion-item>
              <ion-label>Permanent Lock</ion-label>
              <ion-toggle  @ion-change="changePermaLock" :key="updateKey" slot="end" :disabled="!settings.s.enableStorageEnctyption" :checked="settings.s.encryptAfterEveryTx"></ion-toggle>
            </ion-item>
            <ion-item>Will require decrypt pass before any sign or transaction</ion-item>
          </ion-list>
        </ion-list>
    </div>
  </ion-accordion>
  <ion-accordion value="2">
    <ion-item slot="header" color="light">
      <ion-label>Theme</ion-label>
    </ion-item>
    <div class="ion-padding" slot="content">
      <ion-list>
              <ion-radio-group :value="radioTheme">
                  <ion-item>
                    <ion-radio
                      slot="start"
                      value="system"
                      @click="changeTheme('system')"
                    />
                    <ion-label>System Default</ion-label>
                  </ion-item>
                  <ion-item>
                    <ion-radio
                      slot="start"
                      value="dark"
                      @click="changeTheme('dark')"
                    />
                    <ion-label>Dark</ion-label>
                  </ion-item>
                  <ion-item>
                    <ion-radio
                      slot="start"
                      value="light"
                      @click="changeTheme('light')"
                    />
                    <ion-label>Light</ion-label>
                  </ion-item>
              </ion-radio-group>
          </ion-list>
    </div>
  </ion-accordion>
  <ion-accordion value="3">
    <ion-item slot="header" color="light">
      <ion-label>About</ion-label>
    </ion-item>
    <div class="ion-padding" slot="content">
      <p>Clear EVM Wallet (CLW) is a fully open-source wallet built with Vue, Ionic, and Ethers.</p>
      <p>It emulates Metamask Wallet and can be used as a drop-in replacement, right now if you have both extensions, CLW will overwrite Metamask.</p>
      <p>Main philosophy of the wallet is: no trackers, full control, export/import JSONs with accounts, fast generate new accounts, and wipe everything with one click.</p>
      <p>Github Repo: <a href="#" @click="openTab('https://github.com/andrei0x309/clear-wallet')">LINK</a></p>
      <br/>
      <p style="margin-bottom: 0.2rem">Some Web3 Projects I personally appreciate:</p>
      <p>YUP - web3 social platform <a href="#" @click="openTab('https://app.yup.io')">LINK</a></p>
      <p>Crypto-Leftists: web3 left-wing crypto community <a href="#" @click="openTab('https://discord.gg/gzA4bTCdhb')">LINK</a></p>
      <p>Idena: web3 fully private identity provider blockchain <a href="#" @click="openTab('https://www.idena.io/')">LINK</a></p>
      <p>Mirror: web3 publishing platform <a href="#" @click="openTab('https://mirror.xyz')">LINK</a></p>
    </div>
  </ion-accordion>
  <ion-accordion value="4">
    <ion-item slot="header" color="light">
      <ion-label> Import / Export Accounts</ion-label>
    </ion-item>
    <div class="ion-padding" slot="content">
      <ion-item>
          <ion-label>Import Additional Accounts</ion-label>
          <input ref="importFile" type="file"  accept=".json" />
          <ion-button color="warning" @click="importAcc">Import</ion-button>
        </ion-item>
        <ion-item>
          <ion-label>Export All Accounts</ion-label>
          <ion-button color="warning" @click="exportAcc">Export</ion-button>
        </ion-item>
    </div>
  </ion-accordion>
  <ion-accordion value="5">
    <ion-item slot="header" color="light">
      <ion-label>Danger</ion-label>
    </ion-item>
    <div class="ion-padding" slot="content">
      <ion-item>
          <ion-label>WIPE All DATA</ion-label>
          <ion-button color="danger" @click="wipeStorage">PERMA WIPE</ion-button>
        </ion-item>
    </div>
  </ion-accordion>
</ion-accordion-group>
        <ion-toast
        :is-open="toastState"
        @didDismiss="toastState = false"
        :message="toastMsg"
        :duration="1500"
      ></ion-toast>
      <ion-loading
        :is-open="loading"
        cssClass="my-custom-class"
        message="Please wait..."
        :duration="4000"
        @didDismiss="loading = false"
      >
      </ion-loading>
      <ion-modal
        :is-open="mpModal"
        @did-dismiss="mpModal=false;modalDismiss()"
      >
          <ion-header>
            <ion-toolbar>
              <ion-buttons slot="start">
                <ion-button @click="modalGetPassword?.reject ? (() => { modalGetPassword.reject(); modalGetPassword = null })() : mpModal=false">Close</ion-button>
              </ion-buttons>
              <ion-title v-if="!settings.s.enableStorageEnctyption">Create Encryption Password</ion-title>
              <ion-title v-else>Enter Encryption Password</ion-title>
            </ion-toolbar>
          </ion-header>
          <ion-content class="ion-padding">
            <ion-list v-if="settings.s.enableStorageEnctyption">
          <ion-item>
            <ion-label>Old Password</ion-label>
          </ion-item> <ion-item>
          <ion-input v-model="mpPass" type="password"></ion-input>
        </ion-item>
      </ion-list>
      <div v-else>
            <ion-list>
          <ion-item>
            <ion-label>New Password</ion-label>
          </ion-item> <ion-item>
          <ion-input v-model="mpPass" type="password"></ion-input>
        </ion-item>
      </ion-list>
      <ion-list>
          <ion-item>
            <ion-label>Confirm</ion-label>
          </ion-item> <ion-item>
          <ion-input v-model="mpConfirm" type="password"></ion-input>
        </ion-item>
      </ion-list>
    </div>
      <ion-item>
          <ion-button @click="modalGetPassword?.resolve ? (() => { modalGetPassword.resolve(); modalGetPassword = null })() : confirmModal()">Confirm</ion-button>
        </ion-item>
          </ion-content>
      </ion-modal>
      <ion-alert
      :is-open="alertOpen"
      :header="alertHeader"
      :message="alertMsg"
      :buttons="['OK']"
      @didDismiss="alertOpen=false"
    ></ion-alert>
    </ion-content>
  </ion-page>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, Ref } from "vue";
import { storageWipe, getSettings, setSettings, getAccounts, saveSelectedAccount, replaceAccounts, openTab } from "@/utils/platform";
import { decrypt, encrypt, getCryptoParams } from "@/utils/webCrypto"
import { Account } from '@/extension/types'
import { exportFile } from '@/utils/misc'
import type { Settings } from "@/extension/types"
import {
  IonContent,
  IonHeader,
  IonPage,
  IonTitle,
  IonToolbar,
  IonItem,
  IonLabel,
  IonButton,
  IonLoading,
  onIonViewWillEnter,
  IonList,
  IonToggle,
  IonModal,
  IonInput,
  IonAccordion,
  IonAccordionGroup,
  IonRadioGroup,
  IonRadio,
  IonButtons,
  IonAlert,
  IonToast
} from "@ionic/vue";

export default defineComponent({
  components: {
    IonContent,
    IonHeader,
    IonPage,
    IonTitle,
    IonToolbar,
    IonItem,
    IonLabel,
    IonButton,
    IonLoading,
    IonList,
    IonToggle,
    IonModal,
    IonInput,
    IonAccordion,
    IonAccordionGroup,
    IonRadioGroup,
    IonRadio,
    IonButtons,
    IonAlert,
    IonToast
  },
  setup() {
    const loading = ref(true);
    const mpModal = ref(false);
    const mpPass = ref('');
    const mpConfirm = ref('');
    const updateKey = ref(0);
    const alertOpen = ref(false);
    const alertMsg = ref('');
    const toastState = ref(false);
    const toastMsg = ref('');
    const alertHeader = ref('Error')
    const importFile = ref(null) as unknown as Ref<HTMLInputElement>
    type ModalPromisePassword =  null | { resolve: ((p?: unknown) => void), reject: ((p?: unknown) => void)}
    const modalGetPassword = ref(null) as Ref<ModalPromisePassword>
    const noAccounts = ref(true)
    const defaultAccordionOpen = ref("0")
    const radioTheme = ref('system') as Ref<'system' | 'light' | 'dark'>

    const wipeStorage = async () => {
      loading.value = true;
      await storageWipe();
      loading.value = false;
    };
    const settings = reactive({
      s: null as unknown as Settings
    }) as { s: Settings}
    
    const saveSettings = async () => {
      loading.value = true
      await setSettings(settings.s)
      loading.value = false
    }

    const setEncryptToggle = (state: boolean) => {
      settings.s.enableStorageEnctyption = state
      updateKey.value++
      defaultAccordionOpen.value = "1"
    }

    const changeAutoLock = async () => {
      settings.s.lockOutEnabled = !settings.s.lockOutEnabled
      updateKey.value++
      await saveSettings()
      defaultAccordionOpen.value = "1"
    }

    const changePermaLock = async () => {
      settings.s.encryptAfterEveryTx = !settings.s.encryptAfterEveryTx
      updateKey.value++
      await saveSettings()
      defaultAccordionOpen.value = "1"
    }

    const changeTheme = async (theme: 'system' | 'light' | 'dark') => {
      document.body.classList.remove(radioTheme.value)
      document.body.classList.add(theme)
      radioTheme.value = theme
      settings.s.theme = theme
      await saveSettings()
      defaultAccordionOpen.value = "2"
    }

    const changeEncryption = async () => {
      loading.value = true
      mpModal.value = true
      loading.value = false
    }

    const confirmModal = async () => {
      loading.value = true
      if(mpPass.value.length  < 3) {
        loading.value = false
        alertHeader.value = 'Error'
        alertMsg.value = 'Password is too short. More than 3 characters are required.';
        alertOpen.value = true
        setEncryptToggle(settings.s.enableStorageEnctyption)
        return
      }

      if (!settings.s.enableStorageEnctyption) {
        if (mpPass.value !== mpConfirm.value) {
        loading.value = false
        alertHeader.value = 'Error'
        alertMsg.value = 'Password and confirm password do not match';
        alertOpen.value = true
        setEncryptToggle(settings.s.enableStorageEnctyption)
        return
      }
      let accounts = await getAccounts()
      const cryptoParams = await getCryptoParams(mpPass.value)
      const accProm = accounts.map(async a => {
        a.encPk = await encrypt(a.pk, cryptoParams)
        a.pk = ''
        return a
      })
      accounts = await Promise.all(accProm)
      await replaceAccounts(accounts)
      await saveSelectedAccount(accounts[0])
      setEncryptToggle(true)
      await setSettings(settings.s)
      mpPass.value = ''
      mpConfirm.value = ''
      mpModal.value = false
      } else {
        try {
        let accounts = await getAccounts()
        const cryptoParams = await getCryptoParams(mpPass.value)
        const accProm = accounts.map(async a => {
          if(a.encPk) {
            a.pk = await decrypt(a.encPk, cryptoParams)
          }
          return a
        })
        accProm.forEach( a => a.catch(e => console.log(e)) )
        accounts = await Promise.all(accProm)
        await replaceAccounts(accounts)
        await saveSelectedAccount(accounts[0])
        setEncryptToggle(false)
        settings.s.lockOutEnabled = false
        settings.s.encryptAfterEveryTx = false
        await setSettings(settings.s)
        mpPass.value = ''
        mpConfirm.value = ''
        mpModal.value = false
        } catch(error) {
        console.log(error)
        loading.value = false
        alertHeader.value = 'Error'
        alertMsg.value = 'Decryption failed, password is not correct.';
        alertOpen.value = true
        setEncryptToggle(settings.s.enableStorageEnctyption)
        return
        }
      }

    loading.value = false
    }

    const validateFile = () => {
      return new Promise((resolve) => {
      try {
        if (!importFile.value?.value?.length) {
          return resolve({ 
            error: 'Import json file is missing'
          })
        }
        const reader = new FileReader();
        reader.onload = (event) => {
          const json = JSON.parse(event?.target?.result as string)
          if(!json.length){
            return resolve({ error: 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}]' })
          }
          const test = json.some((e:any) => ( !('pk' in e ) || !('name' in e) || !('address' in e) || !(e.pk.length === 66 || e.pk.length === 64)))
          if(test) {
            return resolve({ error: 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..."  },{...}], Also PK must be valid (66 || 64 length) !' })
          }
          return resolve({ error: false, json })
        }
        reader.readAsText(importFile.value?.files?.[0] as File);
        
      }catch {
        return resolve(
         {
          error: 'Parsing JSON file'
        })
      }
    })
    }

    const getPassword = () => {
      return new Promise( (resolve, reject) => {
        modalGetPassword.value = { resolve, reject }
        mpModal.value = true
      })
    }

    const promptForPassword = async (accounts: Account[]) => {
      let isCorectPass = false
              do {
              try {
              await getPassword()
              modalGetPassword.value = null
            } catch {
              alertHeader.value = 'Error'
              alertMsg.value = "Password is required!"
              alertOpen.value = true
              mpModal.value = false
              return false
            }
            try {
              const cryptoParams = await getCryptoParams(mpPass.value)
              if(accounts?.[0]?.encPk) {
                await decrypt(accounts[0].encPk, cryptoParams)
              }
            isCorectPass = true
              } catch {
                isCorectPass = false
                alertHeader.value = 'Error'
                alertMsg.value = "Password is wrong!"
                alertOpen.value = true
              }
            } while (!isCorectPass);
            return true
    }
    
    const importAcc = async () => {
      const validation = await validateFile() as { error: any }
      if (validation.error) {
        alertMsg.value = validation.error
        alertOpen.value = true
        return 
      }
      const accounts = await getAccounts()
      const newAccounts = (validation as unknown as { json: Account[] }).json
      if(settings.s.enableStorageEnctyption) {
              const hasPass = await promptForPassword(accounts)      
              if(hasPass) {
              const cryptoParams = await getCryptoParams(mpPass.value)
              const accProm = newAccounts.map(async a => {
              if(a.pk.length === 64) {
                a.pk = `0x${a.pk}`
              }
              a.encPk = await encrypt(a.pk, cryptoParams)
              return a
               })
              const encNewAccounts = await Promise.all(accProm)
              await replaceAccounts([...accounts, ...encNewAccounts])
              alertHeader.value = 'Success'
              alertMsg.value = "Successfully imported new accounts."
              alertOpen.value = true
              noAccounts.value = false
              }
              return false
            } else {
              await replaceAccounts([...accounts, ...newAccounts.map( a => { a.encPk = ''; return a })])
              alertHeader.value = 'Success'
              alertMsg.value = "Successfully imported new accounts."
              alertOpen.value = true
              noAccounts.value = false
            }
    }

    const exportAcc = async () => {
      const accounts = await getAccounts()
      if(!accounts.length) {
        alertMsg.value = "You need at least one account to export."
        alertOpen.value = true
      }
      if(settings.s.enableStorageEnctyption) {
        const hasPass = await promptForPassword(accounts)   
        if(hasPass) {
          const cryptoParams = await getCryptoParams(mpPass.value)
          const accProm = accounts.map(async a => {
              a.pk = await decrypt(a.encPk, cryptoParams)
              return a
               })
              const encNewAccounts = await Promise.all(accProm)
              exportFile('wallet_export.json', JSON.stringify(encNewAccounts, null, 2))
        }
        return false
      } else {
        exportFile('wallet_export.json', JSON.stringify(accounts, null, 2))
      }
    }


    onIonViewWillEnter(async () => {
      await Promise.all([getSettings().then((storeSettings) =>
      {
        settings.s = storeSettings
        radioTheme.value = settings.s.theme
      }),
      getAccounts().then((accounts) => {
        if(accounts.length) {
          noAccounts.value = false
        }
      })])
      loading.value = false
    })

    const setTime = async () => {
      loading.value = true
       if ( settings.s.lockOutPeriod < 2 || settings.s.lockOutPeriod > 120){
        loading.value = false
        alertMsg.value = 'Auto-lock period must be between 2 and 120';
        alertOpen.value = true
        return
       }
       settings.s.lockOutPeriod = Math.trunc(settings.s.lockOutPeriod)
       await saveSettings()
       loading.value = false
       toastMsg.value = 'Auto-lock period was set';
       toastState.value = true
    }

    const modalDismiss = () => {
      setEncryptToggle(settings.s.enableStorageEnctyption)
    }

    return {
      wipeStorage,
      loading,
      mpModal,
      settings,
      saveSettings,
      changeEncryption,
      mpPass,
      mpConfirm,
      confirmModal,
      updateKey,
      alertOpen,
      alertMsg,
      modalDismiss,
      setTime,
      toastState,
      toastMsg,
      importAcc,
      exportAcc,
      importFile,
      modalGetPassword,
      noAccounts,
      alertHeader,
      changeAutoLock,
      defaultAccordionOpen,
      changeTheme,
      openTab,
      radioTheme,
      changePermaLock
    };
  },
});
</script>
