<template>
  <div>
    <b-row>
      <b-col>
        <b-card no-body>
          <b-card-header>
            <font-awesome-icon
              fixed-width
              class="mr-1"
              :icon="['fas', 'hashtag']"
            />
            Channels
          </b-card-header>
          <b-list-group flush>
            <b-list-group-item
              v-for="channel in sortedChannels"
              :key="channel"
              class="d-flex align-items-center align-middle"
            >
              <font-awesome-icon
                fixed-width
                class="mr-1"
                :icon="['fas', 'hashtag']"
              />
              {{ channel }}
              <span class="ml-auto mr-2">
                <font-awesome-icon
                  v-if="!generalConfig.channel_has_token[channel]"
                  :id="`channelPublicWarn${channel}`"
                  fixed-width
                  class="ml-1 text-danger"
                  :icon="['fas', 'exclamation-triangle']"
                />
                <font-awesome-icon
                  v-else-if="!hasAllExtendedScopes(channel)"
                  :id="`channelPublicWarn${channel}`"
                  fixed-width
                  class="ml-1 text-warning"
                  :icon="['fas', 'exclamation-triangle']"
                />
                <b-tooltip
                  :target="`channelPublicWarn${channel}`"
                  triggers="hover"
                >
                  <template v-if="!generalConfig.channel_has_token[channel]">
                    Bot is not authorized to access Twitch on behalf of this channels owner (tokens are missing).
                    Click pencil to grant permissions.
                  </template>
                  <template v-else>
                    Channel is missing {{ missingExtendedScopes(channel).length }} extended permissions.
                    Click pencil to change granted permissions.
                  </template>
                </b-tooltip>
              </span>
              <b-button-group size="sm">
                <b-button
                  variant="primary"
                  @click="editChannelPermissions(channel)"
                >
                  <font-awesome-icon
                    fixed-width
                    :icon="['fas', 'pencil-alt']"
                  />
                </b-button>
                <b-button
                  variant="danger"
                  @click="removeChannel(channel)"
                >
                  <font-awesome-icon
                    fixed-width
                    :icon="['fas', 'minus']"
                  />
                </b-button>
              </b-button-group>
            </b-list-group-item>

            <b-list-group-item>
              <b-input-group>
                <b-form-input
                  v-model="models.addChannel"
                  :state="!!validateUserName(models.addChannel)"
                  @keyup.enter="addChannel"
                />
                <b-input-group-append>
                  <b-button
                    variant="success"
                    :disabled="!validateUserName(models.addChannel)"
                    @click="addChannel"
                  >
                    <font-awesome-icon
                      fixed-width
                      class="mr-1"
                      :icon="['fas', 'plus']"
                    />
                    Add
                  </b-button>
                </b-input-group-append>
              </b-input-group>
            </b-list-group-item>
          </b-list-group>
        </b-card>
      </b-col>
      <b-col>
        <b-card
          no-body
          class="mb-3"
        >
          <b-card-header>
            <font-awesome-icon
              fixed-width
              class="mr-1"
              :icon="['fas', 'users']"
            />
            Bot-Editors
          </b-card-header>
          <b-list-group flush>
            <b-list-group-item
              v-for="editor in sortedEditors"
              :key="editor"
              class="d-flex align-items-center align-middle"
            >
              <b-avatar
                class="mr-3"
                :src="userProfiles[editor] ? userProfiles[editor].profile_image_url : ''"
              />
              <span class="mr-auto">{{ userProfiles[editor] ? userProfiles[editor].display_name : editor }}</span>
              <b-button
                size="sm"
                variant="danger"
                @click="removeEditor(editor)"
              >
                <font-awesome-icon
                  fixed-width
                  :icon="['fas', 'minus']"
                />
              </b-button>
            </b-list-group-item>

            <b-list-group-item>
              <b-input-group>
                <b-form-input
                  v-model="models.addEditor"
                  :state="!!validateUserName(models.addEditor)"
                  @keyup.enter="addEditor"
                />
                <b-input-group-append>
                  <b-button
                    variant="success"
                    :disabled="!validateUserName(models.addEditor)"
                    @click="addEditor"
                  >
                    <font-awesome-icon
                      fixed-width
                      class="mr-1"
                      :icon="['fas', 'plus']"
                    />
                    Add
                  </b-button>
                </b-input-group-append>
              </b-input-group>
            </b-list-group-item>
          </b-list-group>
        </b-card>

        <b-card
          no-body
        >
          <b-card-header
            class="d-flex align-items-center align-middle"
          >
            <span class="mr-auto">
              <font-awesome-icon
                fixed-width
                class="mr-1"
                :icon="['fas', 'ticket-alt']"
              />
              Auth-Tokens
            </span>
            <b-button-group size="sm">
              <b-button
                variant="success"
                @click="newAPIToken"
              >
                <font-awesome-icon
                  fixed-width
                  :icon="['fas', 'plus']"
                />
              </b-button>
            </b-button-group>
          </b-card-header>
          <b-list-group flush>
            <b-list-group-item
              v-if="createdAPIToken"
              variant="success"
            >
              Token was created, copy it within 30s as you will not see it again:<br>
              <code>{{ createdAPIToken.token }}</code>
            </b-list-group-item>

            <b-list-group-item
              v-for="(token, uuid) in apiTokens"
              :key="uuid"
              class="d-flex align-items-center align-middle"
            >
              <span class="mr-auto">
                {{ token.name }}<br>
                <b-badge
                  v-for="module in token.modules"
                  :key="module"
                  class="mr-1"
                >{{ module === '*' ? 'ANY' : module }}</b-badge>
              </span>
              <b-button
                size="sm"
                variant="danger"
                @click="removeAPIToken(uuid)"
              >
                <font-awesome-icon
                  fixed-width
                  :icon="['fas', 'minus']"
                />
              </b-button>
            </b-list-group-item>
          </b-list-group>
        </b-card>
      </b-col>
      <b-col>
        <b-card
          no-body
          class="mb-3"
          :border-variant="botConnectionCardVariant"
        >
          <b-card-header
            class="d-flex align-items-center align-middle"
            :header-bg-variant="botConnectionCardVariant"
          >
            <span class="mr-auto">
              <font-awesome-icon
                fixed-width
                class="mr-1"
                :icon="['fas', 'sign-in-alt']"
              />
              Bot Connection
            </span>
            <template v-if="generalConfig.bot_name">
              <code
                id="botUserName"
              >
                {{ generalConfig.bot_name }}
                <b-tooltip
                  target="botUserName"
                  triggers="hover"
                >
                  Twitch Login-Name of the bot user currently authorized
                </b-tooltip>
              </code>
            </template>
            <template v-else>
              <font-awesome-icon
                id="botUserNameDC"
                fixed-width
                class="mr-1 text-danger"
                :icon="['fas', 'unlink']"
              />
              <b-tooltip
                target="botUserNameDC"
                triggers="hover"
              >
                Bot is not currently authorized!
              </b-tooltip>
            </template>
          </b-card-header>

          <b-card-body>
            <p>
              Here you can manage your bots auth-token: it's required to communicate with Twitch Chat and APIs. The access will be valid as long as you don't change the password or revoke the apps permission in your bot account.
            </p>
            <ul>
              <li>Copy the URL provided below</li>
              <li><strong>Open an inkognito tab or different browser you are not logged into Twitch or are logged in with your bot account</strong></li>
              <li>Open the copied URL, sign in with the bot account and accept the permissions</li>
              <li>You will see a message containing the authorized account. If this account is wrong, just start over, the token will be overwritten.</li>
            </ul>
            <p
              v-if="botMissingScopes > 0"
              class="alert alert-warning"
            >
              <font-awesome-icon
                fixed-width
                class="mr-1"
                :icon="['fas', 'exclamation-triangle']"
              />
              Bot is missing {{ botMissingScopes }} of its required scopes which will cause features not to work properly. Please re-authorize the bot using the URL below.
            </p>
            <b-input-group>
              <b-form-input
                placeholder="Loading..."
                readonly
                :value="botAuthTokenURL"
                @focus="$event.target.select()"
              />
              <b-input-group-append>
                <b-button
                  :variant="copyButtonVariant.botConnection"
                  @click="copyAuthURL('botConnection')"
                >
                  <font-awesome-icon
                    fixed-width
                    class="mr-1"
                    :icon="['fas', 'clipboard']"
                  />
                  Copy
                </b-button>
              </b-input-group-append>
            </b-input-group>
          </b-card-body>
        </b-card>
      </b-col>
    </b-row>

    <!-- API-Token Editor -->
    <b-modal
      v-if="showAPITokenEditModal"
      hide-header-close
      :ok-disabled="!validateAPIToken"
      ok-title="Save"
      size="md"
      :visible="showAPITokenEditModal"
      title="New API-Token"
      @hidden="showAPITokenEditModal=false"
      @ok="saveAPIToken"
    >
      <b-form-group
        label="Name"
        label-for="formAPITokenName"
      >
        <b-form-input
          id="formAPITokenName"
          v-model="models.apiToken.name"
          :state="Boolean(models.apiToken.name)"
          type="text"
        />
      </b-form-group>

      <b-form-group
        label="Enabled for Modules"
      >
        <b-form-checkbox-group
          v-model="models.apiToken.modules"
          class="mb-3"
          :options="availableModules"
          text-field="text"
          value-field="value"
        />
      </b-form-group>
    </b-modal>

    <!-- Channel Permission Editor -->
    <b-modal
      v-if="showPermissionEditModal"
      hide-footer
      size="lg"
      title="Edit Permissions for Channel"
      :visible="showPermissionEditModal"
      @hidden="showPermissionEditModal=false"
    >
      <b-row>
        <b-col>
          <p>The bot should be able to&hellip;</p>
          <b-form-checkbox-group
            id="channelPermissions"
            v-model="models.channelPermissions"
            :options="extendedPermissions"
            multiple
            :select-size="extendedPermissions.length"
            stacked
            switches
          />
          <p class="mt-3">
            &hellip;on this channel.
          </p>
        </b-col>
        <b-col>
          <p>
            In order to access non-public information as channel-point redemptions or take actions limited to the channel owner the bot needs additional permissions. The <strong>owner</strong> of the channel needs to grant those!
          </p>
          <ul>
            <li>Select permissions on the left side</li>
            <li>Copy the URL provided below</li>
            <li>Pass the URL to the channel owner and tell them to open it with their personal account logged in</li>
            <li>The bot will display a message containing the updated account</li>
          </ul>
        </b-col>
      </b-row>
      <b-row>
        <b-col>
          <b-input-group>
            <b-form-input
              placeholder="Loading..."
              readonly
              :value="extendedPermissionsURL"
              @focus="$event.target.select()"
            />
            <b-input-group-append>
              <b-button
                :variant="copyButtonVariant.channelPermission"
                @click="copyAuthURL('channelPermission')"
              >
                <font-awesome-icon
                  fixed-width
                  class="mr-1"
                  :icon="['fas', 'clipboard']"
                />
                Copy
              </b-button>
            </b-input-group-append>
          </b-input-group>
        </b-col>
      </b-row>
    </b-modal>
  </div>
</template>

<script>
import * as constants from './const.js'

import axios from 'axios'

export default {
  computed: {
    availableModules() {
      return [
        { text: 'ANY', value: '*' },
        ...[...this.modules || []].sort()
          .filter(m => m !== 'config-editor')
          .map(m => ({ text: m, value: m })),
      ]
    },

    botAuthTokenURL() {
      if (!this.authURLs || !this.authURLs.update_bot_token) {
        return ''
      }

      let scopes = [...this.$root.vars.DefaultBotScopes]

      if (this.generalConfig && this.generalConfig.channel_scopes && this.generalConfig.channel_scopes[this.generalConfig.bot_name]) {
        scopes = [
          ...new Set([
            ...scopes,
            ...this.generalConfig.channel_scopes[this.generalConfig.bot_name],
          ]),
        ]
      }

      const u = new URL(this.authURLs.update_bot_token)
      u.searchParams.set('scope', scopes.join(' '))
      return u.toString()
    },

    botConnectionCardVariant() {
      if (this.$parent.status.overall_status_success) {
        return 'secondary'
      }
      return 'warning'
    },

    botMissingScopes() {
      let missing = 0

      if (!this.generalConfig || !this.generalConfig.channel_scopes || !this.generalConfig.bot_name) {
        return -1
      }

      const grantedScopes = [...this.generalConfig.channel_scopes[this.generalConfig.bot_name] || []]

      for (const scope of this.$root.vars.DefaultBotScopes) {
        if (!grantedScopes.includes(scope)) {
          missing++
        }
      }

      return missing
    },

    extendedPermissions() {
      return Object.keys(this.authURLs.available_extended_scopes || {})
        .map(v => ({ text: this.authURLs.available_extended_scopes[v], value: v }))
        .sort((a, b) => a.value.localeCompare(b.value))
    },

    extendedPermissionsURL() {
      if (!this.authURLs?.update_channel_scopes) {
        return null
      }

      const u = new URL(this.authURLs.update_channel_scopes)
      u.searchParams.set('scope', this.models.channelPermissions.join(' '))
      return u.toString()
    },

    sortedChannels() {
      return [...this.generalConfig?.channels || []].sort((a, b) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase()))
    },

    sortedEditors() {
      return [...this.generalConfig?.bot_editors || []].sort((a, b) => {
        const an = this.userProfiles[a]?.login || a
        const bn = this.userProfiles[b]?.login || b

        return an.localeCompare(bn)
      })
    },

    validateAPIToken() {
      return this.models.apiToken.modules.length > 0 && Boolean(this.models.apiToken.name)
    },
  },

  data() {
    return {
      apiTokens: {},
      authURLs: {},
      copyButtonVariant: {
        botConnection: 'primary',
        channelPermission: 'primary',
      },

      createdAPIToken: null,
      generalConfig: {},
      models: {
        addChannel: '',
        addEditor: '',
        apiToken: {},
        channelPermissions: [],
      },

      modules: [],

      showAPITokenEditModal: false,
      showPermissionEditModal: false,
      userProfiles: {},
    }
  },

  methods: {
    addChannel() {
      if (!this.validateUserName(this.models.addChannel)) {
        return
      }

      this.generalConfig.channels.push(this.models.addChannel.replace(/^#*/, ''))
      this.models.addChannel = ''

      this.updateGeneralConfig()
    },

    addEditor() {
      if (!this.validateUserName(this.models.addEditor)) {
        return
      }

      this.fetchProfile(this.models.addEditor)
      this.generalConfig.bot_editors.push(this.models.addEditor)
      this.models.addEditor = ''

      this.updateGeneralConfig()
    },

    copyAuthURL(type) {
      let prom = null
      let btnField = null

      switch (type) {
      case 'botConnection':
        prom = navigator.clipboard.writeText(this.botAuthTokenURL)
        btnField = 'botConnection'
        break
      case 'channelPermission':
        prom = navigator.clipboard.writeText(this.extendedPermissionsURL)
        btnField = 'channelPermission'
        break
      }

      return prom
        .then(() => {
          this.copyButtonVariant[btnField] = 'success'
        })
        .catch(() => {
          this.copyButtonVariant[btnField] = 'danger'
        })
        .finally(() => {
          window.setTimeout(() => {
            this.copyButtonVariant[btnField] = 'primary'
          }, 2000)
        })
    },

    editChannelPermissions(channel) {
      let permissionSet = [...this.generalConfig.channel_scopes[channel] || []]

      if (channel === this.generalConfig.bot_name) {
        permissionSet = [
          ...permissionSet,
          ...this.$root.vars.DefaultBotScopes,
        ]
      }

      this.models.channelPermissions = [...new Set(permissionSet)]
      this.showPermissionEditModal = true
    },

    fetchAPITokens() {
      this.$bus.$emit(constants.NOTIFY_LOADING_DATA, true)
      return axios.get('config-editor/auth-tokens', this.$root.axiosOptions)
        .then(resp => {
          this.apiTokens = resp.data
        })
        .catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
    },

    fetchAuthURLs() {
      this.$bus.$emit(constants.NOTIFY_LOADING_DATA, true)
      return axios.get('config-editor/auth-urls', this.$root.axiosOptions)
        .then(resp => {
          this.authURLs = resp.data
        })
        .catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
    },

    fetchGeneralConfig() {
      this.$bus.$emit(constants.NOTIFY_LOADING_DATA, true)
      return axios.get('config-editor/general', this.$root.axiosOptions)
        .catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
        .then(resp => {
          this.generalConfig = resp.data

          const promises = []
          for (const editor of this.generalConfig.bot_editors) {
            promises.push(this.fetchProfile(editor))
          }

          return Promise.all(promises)
        })
    },

    fetchModules() {
      this.$bus.$emit(constants.NOTIFY_LOADING_DATA, true)
      return axios.get('config-editor/modules')
        .then(resp => {
          this.modules = resp.data
        })
        .catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
    },

    fetchProfile(user) {
      this.$bus.$emit(constants.NOTIFY_LOADING_DATA, true)
      return axios.get(`config-editor/user?user=${user}`, this.$root.axiosOptions)
        .then(resp => {
          this.$set(this.userProfiles, user, resp.data)
          this.$bus.$emit(constants.NOTIFY_LOADING_DATA, false)
        })
        .catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
    },

    hasAllExtendedScopes(channel) {
      if (!this.generalConfig.channel_scopes[channel]) {
        return false
      }

      for (const scope in this.authURLs.available_extended_scopes) {
        if (!this.generalConfig.channel_scopes[channel].includes(scope)) {
          return false
        }
      }

      return true
    },

    missingExtendedScopes(channel) {
      if (!this.generalConfig.channel_scopes[channel]) {
        return Object.keys(this.authURLs.available_extended_scopes || {})
      }

      const missing = []

      for (const scope in this.authURLs.available_extended_scopes) {
        if (!this.generalConfig.channel_scopes[channel].includes(scope)) {
          missing.push(scope)
        }
      }

      return missing
    },

    newAPIToken() {
      this.$set(this.models, 'apiToken', {
        modules: [],
        name: '',
      })
      this.showAPITokenEditModal = true
    },

    removeAPIToken(uuid) {
      axios.delete(`config-editor/auth-tokens/${uuid}`, this.$root.axiosOptions)
        .then(() => {
          this.$bus.$emit(constants.NOTIFY_CHANGE_PENDING, true)
        })
        .catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
    },

    removeChannel(channel) {
      this.generalConfig.channels = this.generalConfig.channels
        .filter(ch => ch !== channel)

      this.updateGeneralConfig()
    },

    removeEditor(editor) {
      this.generalConfig.bot_editors = this.generalConfig.bot_editors
        .filter(ed => ed !== editor)

      this.updateGeneralConfig()
    },

    saveAPIToken(evt) {
      if (!this.validateAPIToken) {
        evt.preventDefault()
        return
      }

      axios.post(`config-editor/auth-tokens`, this.models.apiToken, this.$root.axiosOptions)
        .then(resp => {
          this.createdAPIToken = resp.data
          this.$bus.$emit(constants.NOTIFY_CHANGE_PENDING, true)
          window.setTimeout(() => {
            this.createdAPIToken = null
          }, 30000)
        })
        .catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
    },

    updateGeneralConfig() {
      axios.put('config-editor/general', this.generalConfig, this.$root.axiosOptions)
        .then(() => {
          this.$bus.$emit(constants.NOTIFY_CHANGE_PENDING, true)
        })
        .catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
    },

    validateUserName(user) {
      return user.match(constants.REGEXP_USER)
    },
  },

  mounted() {
    this.$bus.$on(constants.NOTIFY_CONFIG_RELOAD, () => {
      Promise.all([
        this.fetchGeneralConfig(),
        this.fetchAPITokens(),
        this.fetchAuthURLs(),
      ]).then(() => {
        this.$bus.$emit(constants.NOTIFY_CHANGE_PENDING, false)
        this.$bus.$emit(constants.NOTIFY_LOADING_DATA, false)
      })
    })

    Promise.all([
      this.fetchGeneralConfig(),
      this.fetchAPITokens(),
      this.fetchAuthURLs(),
      this.fetchModules(),
    ]).then(() => this.$bus.$emit(constants.NOTIFY_LOADING_DATA, false))
  },

  name: 'TwitchBotEditorAppGeneralConfig',
}
</script>