<script setup>

import { ref, onMounted } from 'vue';
import { Button, PasswordInput, TextInput, fetcher, FormGroup } from '@cloudron/pankow';
import { startAuthentication } from '@simplewebauthn/browser';
import PublicPageLayout from '../components/PublicPageLayout.vue';

const ready = ref(false);
const busy = ref(false);
const passwordError = ref(null);
const totpError = ref(null);
const passkeyError = ref(null);
const internalError = ref(null);
const oidcError = ref('');
const username = ref('');
const password = ref('');
const totpToken = ref('');
const passkeyOptions = ref(null);
const totpRequired = ref(false);
const twoFARequired = ref(false);

// coming from oidc_login.html template
const name = window.cloudron.name;
const note = window.cloudron.note;
const iconUrl = window.cloudron.iconUrl;
const footer = window.cloudron.footer;
const submitUrl = window.cloudron.submitUrl;
const passkeyAuthOptionsUrl = window.cloudron.passkeyAuthOptionsUrl;
const passkeyLoginUrl = window.cloudron.passkeyLoginUrl;

async function onSubmit() {
  busy.value = true;
  passwordError.value = false;
  totpError.value = false;
  passkeyError.value = false;
  internalError.value = false;
  oidcError.value = '';

  const body = {
    username: username.value,
    password: password.value,
    totpToken: totpToken.value
  };

  try {
    const res = await fetcher.post(submitUrl, body);
    if (res.status === 410) {
      // the oidc login session is old
      window.location.reload();
    } else if (res.status === 401) {
      // Authentication failed (wrong password, invalid TOTP, or invalid passkey)
      if (res.body.message && res.body.message.indexOf('passkey') !== -1) {
        passkeyError.value = true;
      } else if (res.body.message && res.body.message.indexOf('totpToken') !== -1) {
        totpError.value = true;
      } else {
        password.value = '';
        passwordError.value = true;
        setTimeout(() => document.getElementById('inputPassword')?.focus(), 0);
      }
    } else if (res.status === 200) {
      // Check if 2FA is required
      if (res.body.twoFactorRequired) {
        twoFARequired.value = true;
        passkeyOptions.value = res.body.passkeyOptions || null;
        totpRequired.value = !!res.body.totpRequired;
        totpToken.value = '';

        if (passkeyOptions.value) await onUsePasskey();
        else if (totpRequired.value) setTimeout(() => document.getElementById('inputTotpToken')?.focus(), 0);
      } else if (res.body.redirectTo) {
        return window.location.href = res.body.redirectTo;
      } else {
        console.error('login success but missing redirectTo in data:', res.body);
        internalError.value = true;
      }
    } else if (res.status >= 400 && res.status < 500) {
      oidcError.value = 'OpenID Error: ' + (res.body.message || '') + '. Will reload in 5 seconds';
      setTimeout(() => window.location.href = '/', 5000);
    } else {
      internalError.value = true;
    }
  } catch (error) {
    internalError.value = true;
    console.error(error);
  }

  busy.value = false;
}

async function onUsePasskey() {
  if (!passkeyOptions.value) return;

  busy.value = true;
  passkeyError.value = false;
  totpError.value = false;
  internalError.value = false;

  try {
    // Start WebAuthn authentication ceremony
    const credential = await startAuthentication({ optionsJSON: passkeyOptions.value });

    // Submit with passkey response
    const body = {
      username: username.value,
      password: password.value,
      passkeyResponse: credential
    };

    const res = await fetcher.post(submitUrl, body);

    if (res.status === 200 && res.body.redirectTo) {
      window.location.href = res.body.redirectTo;
      return;
    } else if (res.status === 401) {
      passkeyError.value = true;
    } else {
      internalError.value = true;
    }
  } catch (error) {
    console.error('Passkey authentication failed', error);
    passkeyError.value = true;
  }

  busy.value = false;
}

async function onLoginWithPasskey() {
  busy.value = true;
  passkeyError.value = false;
  internalError.value = false;

  try {
    const optionsRes = await fetcher.post(passkeyAuthOptionsUrl, {});
    if (optionsRes.status !== 200) {
      internalError.value = true;
      busy.value = false;
      return;
    }

    const credential = await startAuthentication({ optionsJSON: optionsRes.body });

    const res = await fetcher.post(passkeyLoginUrl, { passkeyResponse: credential });

    if (res.status === 200 && res.body.redirectTo) {
      window.location.href = res.body.redirectTo;
      return;
    } else if (res.status === 401) {
      passkeyError.value = true;
    } else {
      internalError.value = true;
    }
  } catch (error) {
    if (error.name !== 'NotAllowedError') {
      console.error('Passkey login failed', error);
      passkeyError.value = true;
    }
  }

  busy.value = false;
}

onMounted(async () => {
  // placed optionally in local storage by setupaccount.js
  const autoLoginToken = localStorage.cloudronFirstTimeToken;
  if (autoLoginToken) {
    localStorage.removeItem('cloudronFirstTimeToken');
    try {
      const res = await fetcher.post(submitUrl, { autoLoginToken });
      window.location.href = res.body.redirectTo || '/';
      return;
    } catch (error) {
      console.error('Failed to use autologin token', error);
    }
  }

  ready.value = true;
});

</script>

<template>
  <PublicPageLayout v-if="ready" :footerHtml="footer" :icon-url="iconUrl" :cloudron-name="name">
    <div>
      <h2>{{ $t('login.loginAction') }}</h2>
      <p v-html="note" style="margin-bottom: 8px"></p>

      <form @submit.prevent="onSubmit" v-if="!twoFARequired">
        <fieldset :disabled="busy">
          <input type="submit" style="display: none;"/>

          <FormGroup>
            <label for="inputUsername">{{ $t('login.username') }}</label>
            <TextInput id="inputUsername" v-model="username" autofocus required/>
          </FormGroup>

          <FormGroup>
            <div style="display: flex; justify-content: space-between; align-items: baseline;">
              <label for="inputPassword">{{ $t('login.password') }}</label>
              <a href="/passwordreset.html" tabindex="-1" style="font-size: 0.85em;">{{ $t('login.resetPasswordAction') }}</a>
            </div>
            <PasswordInput id="inputPassword" v-model="password" required/>
          </FormGroup>

          <div class="error-label" v-if="passwordError">{{ $t('login.errorIncorrectCredentials') }}</div>
          <div class="error-label" v-if="internalError">{{ $t('login.errorInternal') }}</div>
          <div class="error-label" v-if="oidcError">{{ oidcError }}</div>
        </fieldset>

        <div class="actions">
          <Button id="loginSubmitButton" @click="onSubmit" :disabled="busy || (!username || !password)">{{ $t('login.loginAction') }}</Button>
          <a href="#" @click.prevent="onLoginWithPasskey" tabindex="-1">{{ $t('login.passkeyAction') }}</a>
        </div>
        <div class="error-label" v-if="passkeyError">{{ $t('login.errorPasskeyFailed') }}</div>
      </form>

      <form @submit.prevent="onSubmit" v-if="twoFARequired" autocomplete="off">
        <fieldset :disabled="busy">
          <input type="submit" style="display: none;"/>

          <div v-if="passkeyOptions">
            <Button id="passkeyButton" @click="onUsePasskey" :disabled="busy">{{ $t('login.usePasskeyAction') }}</Button>
            <div class="error-label" v-if="passkeyError">{{ $t('login.errorPasskeyFailed') }}</div>
          </div>

          <div v-if="totpRequired">
            <FormGroup :has-error="totpError">
              <label for="inputTotpToken">{{ $t('login.2faToken') }}</label>
              <TextInput id="inputTotpToken" v-model="totpToken"/>
            </FormGroup>

            <div class="error-label" v-if="totpError">{{ $t('login.errorIncorrect2FAToken') }}</div>

            <div class="actions">
              <Button id="totpTokenSubmitButton" @click="onSubmit" :disabled="busy || !totpToken">{{ $t('login.loginAction') }}</Button>
            </div>
          </div>
        </fieldset>
      </form>
    </div>
  </PublicPageLayout>
</template>

<style scoped>

.actions {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.actions a {
  margin-top: 12px;
  font-size: 0.9em;
}

@media (max-width: 576px) {
  .actions {
    align-items: unset;
  }
}

</style>
