import generator, { Entity } from 'megalodon'
import Timeline, { TimelineState } from './AccountProfile/Timeline'
import Follows, { FollowsState } from './AccountProfile/Follows'
import Followers, { FollowersState } from './AccountProfile/Followers'
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
import { RootState } from '@/store'

type ParsedAccount = {
  username: string
  acct: string
  url: string
}

type SearchAccount = {
  parsedAccount: ParsedAccount
  status: Entity.Status
}

export type AccountProfileState = {
  account: Entity.Account | null
  relationship: Entity.Relationship | null
  loading: boolean
  identityProofs: Array<Entity.IdentityProof>
}

type AccountProfileModule = {
  Followers: FollowersState
  Follows: FollowsState
  Timeline: TimelineState
}

export type AccountProfileModuleState = AccountProfileModule & AccountProfileState

const state = (): AccountProfileState => ({
  account: null,
  relationship: null,
  loading: false,
  identityProofs: []
})

export const MUTATION_TYPES = {
  CHANGE_ACCOUNT: 'changeAccount',
  CHANGE_RELATIONSHIP: 'changeRelationship',
  CHANGE_LOADING: 'changeLoading',
  CHANGE_IDENTITY_PROOFS: 'changeIdentityProofs'
}

const mutations: MutationTree<AccountProfileState> = {
  [MUTATION_TYPES.CHANGE_ACCOUNT]: (state, account: Entity.Account | null) => {
    state.account = account
  },
  [MUTATION_TYPES.CHANGE_RELATIONSHIP]: (state, relationship: Entity.Relationship | null) => {
    state.relationship = relationship
  },
  [MUTATION_TYPES.CHANGE_LOADING]: (state, value: boolean) => {
    state.loading = value
  },
  [MUTATION_TYPES.CHANGE_IDENTITY_PROOFS]: (state, values: Array<Entity.IdentityProof>) => {
    state.identityProofs = values
  }
}

const actions: ActionTree<AccountProfileState, RootState> = {
  fetchAccount: async ({ rootState, dispatch }, accountID: string): Promise<Entity.Account> => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.getAccount(accountID)
    dispatch('identityProofs', res.data)
    return res.data
  },
  searchAccount: async ({ rootState }, searchAccount: SearchAccount): Promise<Entity.Account> => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )

    // Find account in toot
    if (searchAccount.status.in_reply_to_account_id) {
      const res = await client.getAccount(searchAccount.status.in_reply_to_account_id)
      if (res.status === 200) {
        const user = accountMatch([res.data], searchAccount.parsedAccount, rootState.TimelineSpace.account.domain)
        if (user) return user
      }
    }

    // Find account in context
    if (searchAccount.status.in_reply_to_id) {
      const res = await client.getStatusContext(searchAccount.status.id)
      if (res.status === 200) {
        const accounts: Array<Entity.Account> = res.data.ancestors.map(s => s.account).concat(res.data.descendants.map(s => s.account))
        const user = accountMatch(accounts, searchAccount.parsedAccount, rootState.TimelineSpace.account.domain)
        if (user) return user
      }
    }

    // Search account name
    const res = await client.searchAccount(searchAccount.parsedAccount.url, { resolve: true })
    if (res.data.length <= 0) throw new AccountNotFound('empty result')
    const user = accountMatch(res.data, searchAccount.parsedAccount, rootState.TimelineSpace.account.domain)
    if (!user) throw new AccountNotFound('not found')
    return user
  },
  changeAccount: ({ commit, dispatch }, account: Entity.Account) => {
    dispatch('fetchRelationship', account)
    dispatch('identityProofs', account)
    commit(MUTATION_TYPES.CHANGE_ACCOUNT, account)
  },
  fetchRelationship: async ({ commit, rootState }, account: Entity.Account): Promise<Entity.Relationship> => {
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, null)
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.getRelationship(account.id)
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
    return res.data
  },
  reload: async ({ dispatch, state, commit }) => {
    commit(MUTATION_TYPES.CHANGE_LOADING, true)
    Promise.all([
      dispatch('fetchRelationship', state.account),
      dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/fetchTimeline', state.account, { root: true }),
      dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/fetchTimeline', state.account, { root: true }),
      dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/fetchTimeline', state.account, { root: true }),
      dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchFollowers', state.account, { root: true }),
      dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchFollows', state.account, { root: true })
    ]).finally(() => {
      commit(MUTATION_TYPES.CHANGE_LOADING, false)
    })
  },
  follow: async ({ commit, rootState, dispatch }, account: Entity.Account) => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.followAccount(account.id)
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
    dispatch('fetchRelationship', account)
    return res.data
  },
  unfollow: async ({ commit, rootState, dispatch }, account: Entity.Account) => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.unfollowAccount(account.id)
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
    dispatch('fetchRelationship', account)
    return res.data
  },
  close: ({ commit }) => {
    commit(MUTATION_TYPES.CHANGE_ACCOUNT, null)
  },
  unmute: async ({ rootState, commit, dispatch }, account: Entity.Account) => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.unmuteAccount(account.id)
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
    dispatch('fetchRelationship', account)
    return res.data
  },
  block: async ({ rootState, commit, dispatch }, account: Entity.Account) => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.blockAccount(account.id)
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
    dispatch('fetchRelationship', account)
    return res.data
  },
  unblock: async ({ rootState, commit, dispatch }, account: Entity.Account) => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.unblockAccount(account.id)
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
    dispatch('fetchRelationship', account)
    return res.data
  },
  identityProofs: async ({ rootState, commit }, account: Entity.Account) => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.getIdentityProof(account.id)
    commit(MUTATION_TYPES.CHANGE_IDENTITY_PROOFS, res.data)
  },
  subscribe: async ({ rootState, commit }, account: Entity.Account) => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.subscribeAccount(account.id)
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
  },
  unsubscribe: async ({ rootState, commit }, account: Entity.Account) => {
    const client = generator(
      rootState.TimelineSpace.sns,
      rootState.TimelineSpace.account.baseURL,
      rootState.TimelineSpace.account.accessToken,
      rootState.App.userAgent
    )
    const res = await client.unsubscribeAccount(account.id)
    commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
  }
}

const getters: GetterTree<AccountProfileState, RootState> = {
  isOwnProfile: (state, _getters, rootState) => {
    if (!state.account) {
      return false
    }
    const own = rootState.TimelineSpace.account
    // For Mastodon
    if (`${own.baseURL}/@${own.username}` === state.account.url) {
      return true
    }
    // For Pleroma
    return `${own.baseURL}/users/${own.username}` === state.account.url
  }
}

const AccountProfile: Module<AccountProfileState, RootState> = {
  namespaced: true,
  modules: {
    Follows,
    Followers,
    Timeline
  },
  state: state,
  mutations: mutations,
  actions: actions,
  getters: getters
}

class AccountNotFound extends Error {}

export default AccountProfile

const accountMatch = (findAccounts: Array<Entity.Account>, parsedAccount: ParsedAccount, domain: string): Entity.Account | false => {
  const account = findAccounts.find(a => `@${a.acct}` === parsedAccount.acct)
  if (account) return account
  const pleromaUser = findAccounts.find(a => a.acct === parsedAccount.acct)
  if (pleromaUser) return pleromaUser
  const localUser = findAccounts.find(a => `@${a.username}@${domain}` === parsedAccount.acct)
  if (localUser) return localUser
  const user = findAccounts.find(a => a.url === parsedAccount.url)
  if (!user) return false
  return user
}
