const fetch = require("node-fetch")
const dns = require("dns")
const punycode = require("punycode")


const lookupDomainIp = domain => new Promise(resolve =>
  dns.lookup(punycode.toASCII(domain), {all: true, hints: dns.ADDRCONFIG | dns.V4MAPPED}, (err, res) =>
    err ? (console.error(err), resolve(null)) : resolve(res[0].address)
  )
)

const nsDomain = domain => new Promise((resolve, reject) =>
  dns.resolveNs(punycode.toASCII(domain), (err, addresses) => err ? reject(err) : resolve(addresses))
)

const NS = ['ns1.weblium.com', 'ns2.weblium.com', 'ns3.weblium.com', 'ns4.weblium.com']

module.exports = async function ping(api) {
  async function pingDomain(domain) {
    async function assert(expression, message, category) {
      if(expression) return
      process.stdout.write(`Domain ${domain._id}. ${"" + message}\n`)
      await api.domain.ping({name: domain._id, data: {category, message}})
      throw message
    }

    const {data: bucketUrl} = await api.api.BUCKET_URL()
    let {data: WEBLIUM_SITE} = await api.api.WEBLIUM_SITE().catch(() => ({}))
    WEBLIUM_SITE = WEBLIUM_SITE || api.host.match(/^.*\/\/([^/]+)(?:\/.*)?$/)[1]
    const webliumIp = await lookupDomainIp(api.host.replace(/^[^/]*\/\//, "www."))
    try {
      const {data: website} = await api.website.get({
        _id: domain.website._id, query: {select: "render subdomain expiredAt suspended.at maintenance"}
      })
      await assert(website, "website not found", "error.websiteNotFound")
      await assert(website.render, "Website has no render", "warning.hasNoRender")
      await assert(+website.expiredAt > Date.now(), "Website expired", "warning.expired")
      await assert(!website.suspended, "Website is suspended", "warning.suspended")
      await assert(!website.maintenance, "Website maintenance", "warning.maintenance")
      const ip = await lookupDomainIp(domain._id)
      await assert(ip == webliumIp, `Invalid IP: "${ip}" != "${webliumIp}"`, "error.invalidIp")
      const ns = await nsDomain(domain._id)
      await assert(ns.find(record => NS.includes(record)), `Invalid ns records ${JSON.stringify(ns)}`, "error.wrongNs")
      let res = await fetch(`https://${domain._id}`, {timeout: 15000})
      await assert(
        res.status == 200, `Can not load html from domain. ${res.status}: ${res.statusText}`, "error.domainGet"
      )
      const dhtml = await res.text()

      res = await fetch(`https:${bucketUrl}/site/${website.render}/index.html`, {timeout: 15000, redirect: "manual"})
      await assert(
        res.status == 200, `Can not load html from bucket. ${res.status}: ${res.statusText}`, "error.bucketGet"
      )
      const bhtml = await res.text()

      await assert(dhtml == bhtml, "Html content error", "error.htmlContent")

      res = await fetch(`https://${website.subdomain}.${WEBLIUM_SITE}`, {timeout: 15000, redirect: "manual"})
      await assert(
        res.status == 301, `Can not load html from subdomain. ${res.status}: ${res.statusText}`, "error.subdomainGet"
      )
      if(res.headers.get("location") != `https://${domain._id}/`) {
        const redirect = res.headers.get("location").replace(/^.*\/\/([^/]*)\/.*$/, "$1")
        const {data: to} = await api.domain.get({_id: redirect})
        await assert(to,
          `Invalid subdomain redirect ${res.status}: ${res.statusText} ${res.headers.get("location")}`,
          "error.subdomainRedirect"
        )
        await assert(
          to.website._id == domain.website._id,
          `Subdomain redirects to other website ${res.status}: ${res.statusText} ${res.headers.get("location")}`,
          "error.redirectToOther"
        )
      }

      process.stdout.write(`Domain ${domain._id}. OK!\n`)
      await api.domain.ping({name: domain._id, data: {category: "ok"}})
    } catch(error) {
      await assert(error instanceof String, "" + error, "exception").catch(() => {})
    }
  }

  for(
    let list = await api.domain.list({query: {filter: {website: {$ne: null}}}, sort: "_id", limit: 5});
    list.data.length;
    list = await api.domain.list({
      query: {filter: {website: {$ne: null}, _id: {$gt: list.data.pop()._id}}}, sort: "_id", limit: 5
    })
  ) {
    await Promise.all(list.data.map(pingDomain))
  }
}
