import * as api from '@/api'
import gradySays from '../grady_speak'
import { BareActionContext, getStoreBuilder } from 'vuex-typex'
import { UserAccount } from '@/models'
import { RootState } from '@/store/store'

export interface Credentials {
    username: string,
    password: string
}

export interface AuthState {
    token: string,
    lastTokenRefreshTry: number,
    refreshingToken: boolean,
    jwtTimeDelta: number,
    message: string,
    user: UserAccount
}
function initialState (): AuthState {
  return {
    token: window.sessionStorage.getItem('token') || '',
    lastTokenRefreshTry: Date.now(),
    refreshingToken: false,
    jwtTimeDelta: 0,
    message: '',
    user: {
      pk: '',
      username: '',
      isAdmin: false
    }
  }
}

const mb = getStoreBuilder<RootState>().module('Authentication', initialState())

const stateGetter = mb.state()

const gradySpeakGetter = mb.read(function gradySpeak () {
  return gradySays[Math.floor(Math.random() * gradySays.length)]
})
const isStudentGetter = mb.read(function isStudent (state: AuthState) {
  return state.user.role === UserAccount.RoleEnum.Student
})
const isTutorGetter = mb.read(function isTutor (state: AuthState) {
  return state.user.role === UserAccount.RoleEnum.Tutor
})
const isReviewerGetter = mb.read(function isReviewer (state: AuthState) {
  return state.user.role === UserAccount.RoleEnum.Reviewer
})
const isTutorOrReviewerGetter = mb.read(function isTutorOrReviewer (state: AuthState, getters) {
  return getters.isTutor || getters.isReviewer
})
const isLoggedInGetter = mb.read(function isLoggedIn (state: AuthState) {
  return !!state.token
})

function SET_MESSAGE (state: AuthState, message: string) {
  state.message = message
}
function SET_JWT_TOKEN (state: AuthState, token: string) {
  window.sessionStorage.setItem('token', token)
  api.default.defaults.headers['Authorization'] = `JWT ${token}`
  state.token = token
}
function SET_JWT_TIME_DELTA (state: AuthState, timeDelta: number) {
  state.jwtTimeDelta = timeDelta
}
function SET_USER (state: AuthState, user: UserAccount) {
  state.user = user
}
function SET_REFRESHING_TOKEN (state: AuthState, refreshing: boolean) {
  state.refreshingToken = refreshing
}
function SET_LAST_TOKEN_REFRESH_TRY (state: AuthState) {
  state.lastTokenRefreshTry = Date.now()
}
function RESET_STATE (state: AuthState) {
  window.sessionStorage.setItem('token', '')
  Object.assign(state, initialState())
}

async function getJWT (context: BareActionContext<AuthState, RootState>, credentials: Credentials) {
  try {
    const token = await api.fetchJWT(credentials)
    Authentication.SET_JWT_TOKEN(token.token)
  } catch (error) {
    let errorMsg
    if (!error.response) {
      errorMsg = 'Cannot reach server.'
    } else if (error.response.status === 400) {
      errorMsg = 'Unable to log in with provided credentials.'
    } else if (error.response.status === 429) {
      errorMsg = error.response.data.detail
    }
    Authentication.SET_MESSAGE(errorMsg)
    throw errorMsg
  } finally {
    Authentication.SET_LAST_TOKEN_REFRESH_TRY()
  }
}
async function refreshJWT ({ state }: BareActionContext<AuthState, RootState>) {
  Authentication.SET_REFRESHING_TOKEN(true)
  try {
    const token = await api.refreshJWT(state.token)
    Authentication.SET_JWT_TOKEN(token.token)
  } finally {
    Authentication.SET_REFRESHING_TOKEN(false)
    Authentication.SET_LAST_TOKEN_REFRESH_TRY()
  }
}
async function getUser () {
  try {
    const user = await api.getOwnUser()
    Authentication.SET_USER(user)
  } catch (err) {
    Authentication.SET_MESSAGE('Unable to fetch user.')
  }
}
async function getJWTTimeDelta () {
  try {
    const delta = await api.fetchJWTTimeDelta()
    // multiply by 1000 to convert to ms
    Authentication.SET_JWT_TIME_DELTA(delta * 1000)
  } catch (err) {
    console.log(err)
  }
}

export const Authentication = {
  get state () { return stateGetter() },
  get gradySpeak () { return gradySpeakGetter() },
  get isStudent () { return isStudentGetter() },
  get isTutor () { return isTutorGetter() },
  get isReviewer () { return isReviewerGetter() },
  get isTutorOrReviewer () { return isTutorOrReviewerGetter() },
  get isLoggedIn () { return isLoggedInGetter() },

  SET_MESSAGE: mb.commit(SET_MESSAGE),
  SET_JWT_TOKEN: mb.commit(SET_JWT_TOKEN),
  SET_JWT_TIME_DELTA: mb.commit(SET_JWT_TIME_DELTA),
  SET_USER: mb.commit(SET_USER),
  SET_REFRESHING_TOKEN: mb.commit(SET_REFRESHING_TOKEN),
  SET_LAST_TOKEN_REFRESH_TRY: mb.commit(SET_LAST_TOKEN_REFRESH_TRY),
  RESET_STATE: mb.commit(RESET_STATE),

  getJWT: mb.dispatch(getJWT),
  refreshJWT: mb.dispatch(refreshJWT),
  getUser: mb.dispatch(getUser),
  getJWTTimeDelta: mb.dispatch(getJWTTimeDelta)
}