import { neode } from '../../../bootstrap/neo4j'

export const undefinedToNullResolver = list => {
  const resolvers = {}
  list.forEach(key => {
    resolvers[key] = async (parent, params, context, resolveInfo) => {
      return typeof parent[key] === 'undefined' ? null : parent[key]
    }
  })
  return resolvers
}

export default function Resolver(type, options = {}) {
  const instance = neode()
  const {
    idAttribute = 'id',
    undefinedToNull = [],
    boolean = {},
    count = {},
    hasOne = {},
    hasMany = {},
  } = options

  const _hasResolver = (resolvers, { key, connection }, { returnType }) => {
    return async (parent, params, context, resolveInfo) => {
      if (typeof parent[key] !== 'undefined') return parent[key]
      const id = parent[idAttribute]
      const statement = `MATCH(:${type} {${idAttribute}: {id}})${connection} RETURN related`
      const result = await instance.cypher(statement, { id })
      let response = result.records.map(r => r.get('related').properties)
      if (returnType === 'object') response = response[0] || null
      return response
    }
  }

  const booleanResolver = obj => {
    const resolvers = {}
    for (const [key, condition] of Object.entries(obj)) {
      resolvers[key] = async (parent, params, { cypherParams }, resolveInfo) => {
        if (typeof parent[key] !== 'undefined') return parent[key]
        const result = await instance.cypher(
          `
        ${condition.replace('this', 'this {id: $parent.id}')} as ${key}`,
          {
            parent,
            cypherParams,
          },
        )
        const [record] = result.records
        return record.get(key)
      }
    }
    return resolvers
  }

  const countResolver = obj => {
    const resolvers = {}
    for (const [key, connection] of Object.entries(obj)) {
      resolvers[key] = async (parent, params, context, resolveInfo) => {
        if (typeof parent[key] !== 'undefined') return parent[key]
        const id = parent[idAttribute]
        const statement = `
          MATCH(u:${type} {${idAttribute}: {id}})${connection}
          RETURN COUNT(DISTINCT(related)) as count
        `
        const result = await instance.cypher(statement, { id })
        const [response] = result.records.map(r => r.get('count').toNumber())
        return response
      }
    }
    return resolvers
  }

  const hasManyResolver = obj => {
    const resolvers = {}
    for (const [key, connection] of Object.entries(obj)) {
      resolvers[key] = _hasResolver(resolvers, { key, connection }, { returnType: 'iterable' })
    }
    return resolvers
  }

  const hasOneResolver = obj => {
    const resolvers = {}
    for (const [key, connection] of Object.entries(obj)) {
      resolvers[key] = _hasResolver(resolvers, { key, connection }, { returnType: 'object' })
    }
    return resolvers
  }

  const result = {
    ...undefinedToNullResolver(undefinedToNull),
    ...booleanResolver(boolean),
    ...countResolver(count),
    ...hasOneResolver(hasOne),
    ...hasManyResolver(hasMany),
  }
  return result
}
