<script>
  import GoogleLogo from "./_logos/Google.svelte"
  import OidcLogo from "./_logos/OIDC.svelte"
  import MicrosoftLogo from "assets/microsoft-logo.png"
  import Auth0Logo from "assets/auth0-logo.png"
  import OktaLogo from "assets/okta-logo.png"
  import OneLoginLogo from "assets/onelogin-logo.png"
  import OidcLogoPng from "assets/oidc-logo.png"
  import { isEqual, cloneDeep } from "lodash/fp"
  import {
    Button,
    Heading,
    Divider,
    Label,
    notifications,
    Layout,
    Input,
    Body,
    Select,
    Toggle,
    Tag,
    Tags,
    Icon,
    Helpers,
  } from "@budibase/bbui"
  import { onMount } from "svelte"
  import { API } from "api"
  import { organisation, admin } from "stores/portal"

  const ConfigTypes = {
    Google: "google",
    OIDC: "oidc",
  }

  const HasSpacesRegex = /[\\"\s]/

  // Some older google configs contain a manually specified value - retain the functionality to edit the field
  // When there is no value or we are in the cloud - prohibit editing the field, must use platform url to change
  $: googleCallbackUrl = undefined
  $: googleCallbackReadonly = $admin.cloud || !googleCallbackUrl

  // Indicate to user that callback is based on platform url
  // If there is an existing value, indicate that it may be removed to return to default behaviour
  $: googleCallbackTooltip = $admin.cloud
    ? null
    : googleCallbackReadonly
    ? "Vist the organisation page to update the platform URL"
    : "Leave blank to use the default callback URL"

  $: GoogleConfigFields = {
    Google: [
      { name: "clientID", label: "Client ID" },
      { name: "clientSecret", label: "Client secret" },
      {
        name: "callbackURL",
        label: "Callback URL",
        readonly: googleCallbackReadonly,
        tooltip: googleCallbackTooltip,
        placeholder: $organisation.googleCallbackUrl,
        copyButton: true,
      },
    ],
  }

  $: OIDCConfigFields = {
    Oidc: [
      { name: "configUrl", label: "Config URL" },
      { name: "clientID", label: "Client ID" },
      { name: "clientSecret", label: "Client Secret" },
      {
        name: "callbackURL",
        readonly: true,
        tooltip: $admin.cloud
          ? null
          : "Vist the organisation page to update the platform URL",
        label: "Callback URL",
        placeholder: $organisation.oidcCallbackUrl,
        copyButton: true,
      },
    ],
  }

  let iconDropdownOptions = [
    {
      label: "Microsoft",
      value: "Microsoft",
      icon: MicrosoftLogo,
    },
    {
      label: "Okta",
      value: "Okta",
      icon: OktaLogo,
    },
    {
      label: "OneLogin",
      value: "OneLogin",
      icon: OneLoginLogo,
    },
    {
      label: "Auth0",
      value: "Auth0",
      icon: Auth0Logo,
    },
    {
      label: "OIDC",
      value: "Oidc",
      icon: OidcLogoPng,
    },
    {
      label: "Upload your own",
      value: "Upload",
    },
  ]

  let fileinput
  let image

  let google
  let oidc
  const providers = { google, oidc }

  // control the state of the save button depending on whether form has changed
  let originalGoogleDoc
  let originalOidcDoc
  let googleSaveButtonDisabled
  let oidcSaveButtonDisabled
  $: {
    isEqual(providers.google?.config, originalGoogleDoc?.config)
      ? (googleSaveButtonDisabled = true)
      : (googleSaveButtonDisabled = false)
    isEqual(providers.oidc?.config, originalOidcDoc?.config)
      ? (oidcSaveButtonDisabled = true)
      : (oidcSaveButtonDisabled = false)
  }

  // Create a flag so that it will only try to save completed forms
  $: partialGoogle =
    providers.google?.config?.clientID || providers.google?.config?.clientSecret
  $: partialOidc =
    providers.oidc?.config?.configs[0].configUrl ||
    providers.oidc?.config?.configs[0].clientID ||
    providers.oidc?.config?.configs[0].clientSecret
  $: googleComplete =
    providers.google?.config?.clientID && providers.google?.config?.clientSecret
  $: oidcComplete =
    providers.oidc?.config?.configs[0].configUrl &&
    providers.oidc?.config?.configs[0].clientID &&
    providers.oidc?.config?.configs[0].clientSecret

  const onFileSelected = e => {
    let fileName = e.target.files[0].name
    image = e.target.files[0]
    providers.oidc.config.configs[0].logo = fileName
    iconDropdownOptions.unshift({ label: fileName, value: fileName })
  }

  async function save(docs) {
    let calls = []
    // Only if the user has provided an image, upload it
    if (image) {
      let data = new FormData()
      data.append("file", image)
      calls.push(
        API.uploadOIDCLogo({
          name: image.name,
          data,
        })
      )
    }
    docs.forEach(element => {
      // Delete unsupported fields
      delete element.createdAt
      delete element.updatedAt

      if (element.type === ConfigTypes.OIDC) {
        // Add a UUID here so each config is distinguishable when it arrives at the login page
        for (let config of element.config.configs) {
          if (!config.uuid) {
            config.uuid = Helpers.uuid()
          }
          // Callback urls shouldn't be included
          delete config.callbackURL
        }
        if (partialOidc) {
          if (!oidcComplete) {
            notifications.error(
              `Please fill in all required ${ConfigTypes.OIDC} fields`
            )
          } else {
            calls.push(API.saveConfig(element))
            // Turn the save button grey when clicked
            oidcSaveButtonDisabled = true
            originalOidcDoc = cloneDeep(providers.oidc)
          }
        }
      }
      if (element.type === ConfigTypes.Google) {
        if (partialGoogle) {
          if (!googleComplete) {
            notifications.error(
              `Please fill in all required ${ConfigTypes.Google} fields`
            )
          } else {
            calls.push(API.saveConfig(element))
            googleSaveButtonDisabled = true
            originalGoogleDoc = cloneDeep(providers.google)
          }
        }
      }
    })
    if (calls.length) {
      Promise.all(calls)
        .then(data => {
          data.forEach(res => {
            providers[res.type]._rev = res._rev
            providers[res.type]._id = res._id
          })
          notifications.success(`Settings saved`)
        })
        .catch(() => {
          notifications.error("Failed to update auth settings")
        })
    }
  }

  let defaultScopes = ["profile", "email", "offline_access"]

  const refreshScopes = idx => {
    providers.oidc.config.configs[idx]["scopes"] =
      providers.oidc.config.configs[idx]["scopes"]
  }

  let scopesFields = [
    {
      editing: true,
      inputText: null,
      error: null,
    },
  ]

  const copyToClipboard = async value => {
    await Helpers.copyToClipboard(value)
    notifications.success("Copied")
  }

  onMount(async () => {
    try {
      await organisation.init()
    } catch (error) {
      notifications.error("Error getting org config")
    }

    // Fetch Google config
    let googleDoc
    try {
      googleDoc = await API.getConfig(ConfigTypes.Google)
    } catch (error) {
      notifications.error("Error fetching Google OAuth config")
    }
    if (!googleDoc?._id) {
      providers.google = {
        type: ConfigTypes.Google,
        config: { activated: true },
      }
      originalGoogleDoc = cloneDeep(googleDoc)
    } else {
      // Default activated to true for older configs
      if (googleDoc.config.activated === undefined) {
        googleDoc.config.activated = true
      }
      originalGoogleDoc = cloneDeep(googleDoc)
      providers.google = googleDoc
    }
    googleCallbackUrl = providers?.google?.config?.callbackURL

    // Get the list of user uploaded logos and push it to the dropdown options.
    // This needs to be done before the config call so they're available when
    // the dropdown renders.
    let oidcLogos
    try {
      oidcLogos = await API.getOIDCLogos()
    } catch (error) {
      notifications.error("Error fetching OIDC logos")
    }
    if (oidcLogos?.config) {
      const logoKeys = Object.keys(oidcLogos.config)
      logoKeys.map(logoKey => {
        const logoUrl = oidcLogos.config[logoKey]
        iconDropdownOptions.unshift({
          label: logoKey,
          value: logoKey,
          icon: logoUrl,
        })
      })
    }

    // Fetch OIDC config
    let oidcDoc
    try {
      oidcDoc = await API.getConfig(ConfigTypes.OIDC)
    } catch (error) {
      notifications.error("Error fetching OIDC config")
    }
    if (!oidcDoc?._id) {
      providers.oidc = {
        type: ConfigTypes.OIDC,
        config: { configs: [{ activated: true, scopes: defaultScopes }] },
      }
    } else {
      originalOidcDoc = cloneDeep(oidcDoc)
      providers.oidc = oidcDoc
    }
  })
</script>

<Layout noPadding>
  <Layout gap="XS" noPadding>
    <Heading size="M">Authentication</Heading>
    <Body>
      Every budibase app comes with basic authentication (email/password)
      included. You can add additional authentication methods from the options
      below.
    </Body>
  </Layout>
  {#if providers.google}
    <Divider />
    <Layout gap="XS" noPadding>
      <Heading size="S">
        <div class="provider-title">
          <GoogleLogo />
          <span>Google</span>
          <Button
            disabled={googleSaveButtonDisabled}
            size="s"
            cta
            on:click={() => save([providers.google])}
          >
            Save
          </Button>
        </div>
      </Heading>
      <Body size="S">
        To allow users to authenticate using their Google accounts, fill out the
        fields below.
      </Body>
    </Layout>
    <Layout gap="XS" noPadding>
      {#each GoogleConfigFields.Google as field}
        <div class="form-row">
          <Label size="L" tooltip={field.tooltip}>{field.label}</Label>
          <div class="inputContainer">
            <div class="input">
              <Input
                bind:value={providers.google.config[field.name]}
                readonly={field.readonly}
                placeholder={field.placeholder}
              />
            </div>
            {#if field.copyButton}
              <div
                class="copy"
                on:click={() => copyToClipboard(field.placeholder)}
              >
                <Icon size="S" name="Copy" />
              </div>
            {/if}
          </div>
        </div>
      {/each}
      <div class="form-row">
        <Label size="L">Activated</Label>
        <Toggle text="" bind:value={providers.google.config.activated} />
      </div>
    </Layout>
  {/if}
  {#if providers.oidc}
    <Divider />
    <Layout gap="XS" noPadding>
      <Heading size="S">
        <div class="provider-title">
          <OidcLogo />
          <span>OpenID Connect</span>
          <Button
            disabled={oidcSaveButtonDisabled}
            size="s"
            cta
            on:click={() => save([providers.oidc])}
            dataCy={"oidc-save"}
          >
            Save
          </Button>
        </div>
      </Heading>
      <Body size="S">
        To allow users to authenticate using OIDC, fill out the fields below.
      </Body>
    </Layout>
    <Layout gap="XS" noPadding>
      {#each OIDCConfigFields.Oidc as field}
        <div class="form-row">
          <Label size="L" tooltip={field.tooltip}>{field.label}</Label>
          <div class="inputContainer">
            <div class="input">
              <Input
                bind:value={providers.oidc.config.configs[0][field.name]}
                readonly={field.readonly}
                placeholder={field.placeholder}
              />
            </div>
            {#if field.copyButton}
              <div
                class="copy"
                on:click={() => copyToClipboard(field.placeholder)}
              >
                <Icon size="S" name="Copy" />
              </div>
            {/if}
          </div>
        </div>
      {/each}
    </Layout>
    <Layout gap="XS" noPadding>
      <Body size="S">
        To customize your login button, fill out the fields below.
      </Body>
      <div class="form-row">
        <Label size="L">Name</Label>
        <Input bind:value={providers.oidc.config.configs[0].name} />
      </div>
      <div class="form-row">
        <Label size="L">Icon</Label>
        <Select
          label=""
          bind:value={providers.oidc.config.configs[0].logo}
          options={iconDropdownOptions}
          on:change={e => e.detail === "Upload" && fileinput.click()}
        />
      </div>
      <input
        type="file"
        accept=".jpg, .jpeg, .png"
        on:change={e => onFileSelected(e)}
        bind:this={fileinput}
      />
      <div class="form-row">
        <Label size="L">Activated</Label>
        <Toggle
          dataCy={"oidc-active"}
          text=""
          bind:value={providers.oidc.config.configs[0].activated}
        />
      </div>
    </Layout>
    <span class="advanced-config">
      <Layout gap="XS" noPadding>
        <Heading size="XS">
          <div class="auth-scopes">
            <div>Advanced</div>
            <Button
              secondary
              newStyles
              size="S"
              on:click={() => {
                providers.oidc.config.configs[0]["scopes"] = [...defaultScopes]
              }}
              dataCy={"restore-oidc-default-scopes"}
            >
              Restore Defaults
            </Button>
          </div>
        </Heading>
        <Body size="S">
          Changes to your authentication scopes will only take effect when you
          next log in. Please refer to your vendor documentation before
          modification.
        </Body>

        <div class="auth-form">
          <span class="add-new">
            <Label size="L">{"Auth Scopes"}</Label>
            <Input
              dataCy={"new-scope-input"}
              error={scopesFields[0].error}
              placeholder={"New Scope"}
              bind:value={scopesFields[0].inputText}
              on:keyup={e => {
                if (!scopesFields[0].inputText) {
                  scopesFields[0].error = null
                }
                if (
                  e.key === "Enter" ||
                  e.keyCode === 13 ||
                  e.code == "Space" ||
                  e.keyCode == 32
                ) {
                  let scopes = providers.oidc.config.configs[0]["scopes"]
                    ? providers.oidc.config.configs[0]["scopes"]
                    : [...defaultScopes]

                  let update = scopesFields[0].inputText.trim()

                  if (HasSpacesRegex.test(update)) {
                    scopesFields[0].error =
                      "Auth scopes cannot contain spaces, double quotes or backslashes"
                    return
                  } else if (scopes.indexOf(update) > -1) {
                    scopesFields[0].error = "Auth scope already exists"
                    return
                  } else if (!update.length) {
                    scopesFields[0].inputText = null
                    scopesFields[0].error = null
                    return
                  } else {
                    scopesFields[0].error = null
                    scopes.push(update)
                    providers.oidc.config.configs[0]["scopes"] = scopes
                    scopesFields[0].inputText = null
                  }
                }
              }}
            />
          </span>
          <div class="tag-wrap">
            <span />
            <Tags>
              <Tag closable={false}>openid</Tag>
              {#each providers.oidc.config.configs[0]["scopes"] || [...defaultScopes] as tag, idx}
                <Tag
                  closable={scopesFields[0].editing}
                  on:click={() => {
                    let idxScopes = providers.oidc.config.configs[0]["scopes"]
                    if (idxScopes.length == 1) {
                      idxScopes.pop()
                    } else {
                      idxScopes.splice(idx, 1)
                      refreshScopes(0)
                    }
                  }}
                >
                  {tag}
                </Tag>
              {/each}
            </Tags>
          </div>
        </div>
      </Layout>
    </span>
  {/if}
</Layout>

<style>
  .auth-scopes {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .advanced-config :global(.spectrum-Tags-item) {
    margin-left: 0px;
    margin-top: var(--spacing-m);
    margin-right: var(--spacing-m);
  }

  .auth-form > * {
    display: grid;
    grid-gap: var(--spacing-l);
    grid-template-columns: 100px 1fr;
  }

  .advanced-config .auth-form .tag-wrap {
    padding: 0px 5px 5px 0px;
  }

  .form-row {
    display: grid;
    grid-template-columns: 100px 1fr;
    grid-gap: var(--spacing-l);
    align-items: center;
  }

  input[type="file"] {
    display: none;
  }

  .provider-title {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    gap: var(--spacing-m);
  }
  .provider-title span {
    flex: 1 1 auto;
  }
  .inputContainer {
    display: flex;
    flex-direction: row;
  }
  .input {
    flex: 1;
  }
  .copy {
    display: flex;
    align-items: center;
    margin-left: 10px;
  }
</style>
