import { InvalidParametersError } from '@libp2p/interface'
import type { Multiaddr } from '@multiformats/multiaddr'

export interface IP4NetConfig {
  type: 'ip4'
  host: string
  protocol?: 'tcp' | 'udp'
  port?: number
  cidr?: number
  sni?: string
}

export interface IP6NetConfig {
  type: 'ip6'
  host: string
  protocol?: 'tcp' | 'udp'
  port?: number
  zone?: string
  cidr?: string
  sni?: string
}

export interface DNSNetConfig {
  type: 'dns'
  host: string
  protocol?: 'tcp' | 'udp'
  port: number
  cidr?: number
}

export interface DNS4NetConfig {
  type: 'dns4'
  host: string
  protocol?: 'tcp' | 'udp'
  port: number
  cidr?: number
}

export interface DNS6NetConfig {
  type: 'dns6'
  host: string
  protocol?: 'tcp' | 'udp'
  port: number
  cidr?: number
}

export interface DNSAddrNetConfig {
  type: 'dnsaddr'
  host: string
  protocol?: 'tcp' | 'udp'
  port: number
  cidr?: number
}

export type NetConfig = IP4NetConfig | IP6NetConfig | DNSNetConfig | DNS4NetConfig | DNS6NetConfig | DNSAddrNetConfig

/**
 * Returns host/port/etc information for a multiaddr if it starts with a
 * network address (IPv4, IPv6, DNS, DNS4, DNS6 or DNSADDR), or null otherwise.
 */
export function tryGetNetConfig (ma: Multiaddr): NetConfig | null {
  const components = ma.getComponents()
  const config: any = {}
  let index = 0

  if (components[index]?.name === 'ip6zone') {
    config.zone = `${components[index].value}`
    index++
  }

  if (components[index]?.name === 'ip4' || components[index]?.name === 'ip6') {
    config.type = components[index].name
    config.host = components[index].value
    index++
  } else if (components[index]?.name === 'dns' || components[index]?.name === 'dns4' || components[index]?.name === 'dns6') {
    config.type = components[index].name
    config.host = components[index].value
    index++
  } else if (components[index]?.name === 'dnsaddr') {
    config.type = components[index].name
    config.host = `_dnsaddr.${components[index].value}`
    index++
  }

  if (components[index]?.name === 'tcp' || components[index]?.name === 'udp') {
    config.protocol = components[index].name === 'tcp' ? 'tcp' : 'udp'
    config.port = parseInt(`${components[index].value}`)
    index++
  }

  if (components[index]?.name === 'ipcidr') {
    if (config.type === 'ip4') {
      config.cidr = parseInt(`${components[index].value}`)
    } else if (config.type === 'ip6') {
      config.cidr = `${components[index].value}`
    }
    index++
  }

  if (config.type == null || config.host == null) {
    return null
  }

  if (components[index]?.name === 'tls' && components[index + 1]?.name === 'sni') {
    config.sni = components[index + 1].value
    index += 2
  }

  return config
}

/**
 * Like `tryGetNetConfig` but throws `InvalidParametersError` when the multiaddr
 * does not start with a network address.
 */
export function getNetConfig (ma: Multiaddr): NetConfig {
  const config = tryGetNetConfig(ma)

  if (config == null) {
    throw new InvalidParametersError(`Multiaddr ${ma} was not an IPv4, IPv6, DNS, DNS4, DNS6 or DNSADDR address`)
  }

  return config
}
