import { forOwn } from 'lodash'
import { CAVEAT_NAMES } from '../../../app/scripts/controllers/permissions/enums'
import {
  getMetaMaskAccountsOrdered,
  getOriginOfCurrentTab,
  getSelectedAddress,
} from '.'

// selectors

/**
 * Get the permission domains object.
 *
 * @param {Object} state - The current state.
 * @returns {Object} The permissions domains object.
 */
export function getPermissionDomains(state) {
  return state.metamask.domains || {}
}

/**
 * Get the permission domains metadata object.
 *
 * @param {Object} state - The current state.
 * @returns {Object} The permission domains metadata object.
 */
export function getPermissionDomainsMetadata(state) {
  return state.metamask.domainMetadata || {}
}

/**
 * Selects the permitted accounts from the eth_accounts permission given state
 * and an origin.
 *
 * @param {Object} state - The current state.
 * @param {string} origin - The origin/domain to get the permitted accounts for.
 * @returns {Array<string>} An empty array or an array of accounts.
 */
export function getPermittedAccounts(state, origin) {
  return getAccountsFromPermission(
    getAccountsPermissionFromDomain(domainSelector(state, origin)),
  )
}

/**
 * Selects the permitted accounts from the eth_accounts permission for the
 * origin of the current tab.
 *
 * @param {Object} state - The current state.
 * @returns {Array<string>} An empty array or an array of accounts.
 */
export function getPermittedAccountsForCurrentTab(state) {
  return getPermittedAccounts(state, getOriginOfCurrentTab(state))
}

/**
 * Returns a map of permitted accounts by origin for all origins.
 *
 * @param {Object} state - The current state.
 * @returns {Object} Permitted accounts by origin.
 */
export function getPermittedAccountsByOrigin(state) {
  const domains = getPermissionDomains(state)
  return Object.keys(domains).reduce((acc, domainKey) => {
    const accounts = getAccountsFromPermission(
      getAccountsPermissionFromDomain(domains[domainKey]),
    )
    if (accounts.length > 0) {
      acc[domainKey] = accounts
    }
    return acc
  }, {})
}

/**
 * Returns an array of connected domain objects, with the following properties:
 * - extensionId
 * - key (i.e. origin)
 * - name
 * - icon
 *
 * @param {Object} state - The current state.
 * @returns {Array<Object>} An array of connected domain objects.
 */
export function getConnectedDomainsForSelectedAddress(state) {
  const { selectedAddress } = state.metamask
  const domains = getPermissionDomains(state)
  const domainMetadata = getPermissionDomainsMetadata(state)

  const connectedDomains = []

  forOwn(domains, (domainValue, domainKey) => {
    const exposedAccounts = getAccountsFromDomain(domainValue)
    if (!exposedAccounts.includes(selectedAddress)) {
      return
    }

    const { extensionId, name, icon, host } = domainMetadata[domainKey] || {}

    connectedDomains.push({
      extensionId,
      origin: domainKey,
      name,
      icon,
      host,
    })
  })

  return connectedDomains
}

/**
 * Returns an object mapping addresses to objects mapping origins to connected
 * domain info. Domain info objects have the following properties:
 * - icon
 * - name
 *
 * @param {Object} state - The current state.
 * @returns {Object} A mapping of addresses to a mapping of origins to
 * connected domain info.
 */
export function getAddressConnectedDomainMap(state) {
  const domainMetadata = getPermissionDomainsMetadata(state)
  const accountsMap = getPermittedAccountsByOrigin(state)
  const addressConnectedIconMap = {}

  Object.keys(accountsMap).forEach((domainKey) => {
    const { icon, name } = domainMetadata[domainKey] || {}

    accountsMap[domainKey].forEach((address) => {
      const nameToRender = name || domainKey

      addressConnectedIconMap[address] = addressConnectedIconMap[address]
        ? {
            ...addressConnectedIconMap[address],
            [domainKey]: { icon, name: nameToRender },
          }
        : { [domainKey]: { icon, name: nameToRender } }
    })
  })

  return addressConnectedIconMap
}

// selector helpers

function getAccountsFromDomain(domain) {
  return getAccountsFromPermission(getAccountsPermissionFromDomain(domain))
}

function getAccountsPermissionFromDomain(domain = {}) {
  return Array.isArray(domain.permissions)
    ? domain.permissions.find(
        (perm) => perm.parentCapability === 'eth_accounts',
      )
    : {}
}

function getAccountsFromPermission(accountsPermission) {
  const accountsCaveat = getAccountsCaveatFromPermission(accountsPermission)
  return accountsCaveat && Array.isArray(accountsCaveat.value)
    ? accountsCaveat.value
    : []
}

function getAccountsCaveatFromPermission(accountsPermission = {}) {
  return (
    Array.isArray(accountsPermission.caveats) &&
    accountsPermission.caveats.find(
      (c) => c.name === CAVEAT_NAMES.exposedAccounts,
    )
  )
}

function domainSelector(state, origin) {
  return origin && state.metamask.domains?.[origin]
}

export function getAccountToConnectToActiveTab(state) {
  const selectedAddress = getSelectedAddress(state)
  const connectedAccounts = getPermittedAccountsForCurrentTab(state)

  const {
    metamask: { identities },
  } = state
  const numberOfAccounts = Object.keys(identities).length

  if (
    connectedAccounts.length &&
    connectedAccounts.length !== numberOfAccounts
  ) {
    if (
      connectedAccounts.findIndex((address) => address === selectedAddress) ===
      -1
    ) {
      return identities[selectedAddress]
    }
  }

  return undefined
}

export function getOrderedConnectedAccountsForActiveTab(state) {
  const {
    activeTab,
    metamask: { permissionsHistory },
  } = state

  const permissionsHistoryByAccount =
    // eslint-disable-next-line camelcase
    permissionsHistory[activeTab.origin]?.eth_accounts?.accounts
  const orderedAccounts = getMetaMaskAccountsOrdered(state)
  const connectedAccounts = getPermittedAccountsForCurrentTab(state)

  return orderedAccounts
    .filter((account) => connectedAccounts.includes(account.address))
    .map((account) => ({
      ...account,
      lastActive: permissionsHistoryByAccount?.[account.address],
    }))
    .sort(
      ({ lastSelected: lastSelectedA }, { lastSelected: lastSelectedB }) => {
        if (lastSelectedA === lastSelectedB) {
          return 0
        } else if (lastSelectedA === undefined) {
          return 1
        } else if (lastSelectedB === undefined) {
          return -1
        }

        return lastSelectedB - lastSelectedA
      },
    )
}

export function getPermissionsForActiveTab(state) {
  const { activeTab, metamask } = state
  const { domains = {} } = metamask

  return domains[activeTab.origin]?.permissions?.map(({ parentCapability }) => {
    return {
      key: parentCapability,
    }
  })
}

export function getLastConnectedInfo(state) {
  const { permissionsHistory = {} } = state.metamask
  return Object.keys(permissionsHistory).reduce((acc, origin) => {
    const ethAccountsHistory = JSON.parse(
      JSON.stringify(permissionsHistory[origin].eth_accounts),
    )
    return {
      ...acc,
      [origin]: ethAccountsHistory,
    }
  }, {})
}

export function getPermissionsMetadataHostCounts(state) {
  const metadata = getPermissionDomainsMetadata(state)
  return Object.values(metadata).reduce((counts, { host }) => {
    if (host) {
      if (counts[host]) {
        counts[host] += 1
      } else {
        counts[host] = 1
      }
    }
    return counts
  }, {})
}

export function getPermissionsRequests(state) {
  return state.metamask.permissionsRequests || []
}

export function getPermissionsRequestCount(state) {
  const permissionsRequests = getPermissionsRequests(state)
  return permissionsRequests.length
}

export function getFirstPermissionRequest(state) {
  const requests = getPermissionsRequests(state)
  return requests && requests[0] ? requests[0] : null
}
