/*
 * Copyright 2014-2024 Li Kexian
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Go module for domain whois information parsing
 * https://www.likexian.com/
 */

package whoisparser

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/likexian/gokit/assert"
	"github.com/likexian/gokit/xslice"
)

// Prepare do prepare the whois info for parsing
func Prepare(text, ext string) (string, bool) { //nolint:cyclop
	text = strings.Replace(text, "\r", "", -1)
	text = strings.Replace(text, "\t", " ", -1)
	text = strings.TrimSpace(text)

	switch ext {
	case "":
		return prepareTLD(text), true
	case "edu":
		return prepareEDU(text), true
	case "int":
		return prepareINT(text), true
	case "mo":
		return prepareMO(text), true
	case "hk":
		return prepareHK(text), true
	case "tw":
		return prepareTW(text), true
	case "ch":
		return prepareCH(text), true
	case "it":
		return prepareIT(text), true
	case "fr", "re", "tf", "yt", "pm", "wf":
		return prepareFR(text), true
	case "ru", "su", "xn--p1ai":
		return prepareRU(text), true
	case "fi":
		return prepareFI(text), true
	case "jp":
		return prepareJP(text), true
	case "uk":
		return prepareUK(text), true
	case "kr":
		return prepareKR(text), true
	case "nz":
		return prepareNZ(text), true
	case "tk":
		return prepareTK(text), true
	case "nl":
		return prepareNL(text), true
	case "eu":
		return prepareEU(text), true
	case "br":
		return prepareBR(text), true
	case "ir", "xn--mgba3a4f16a":
		return prepareIR(text), true
	case "rs":
		return prepareRS(text), true
	case "kz":
		return prepareKZ(text), true
	case "ee":
		return prepareEE(text), true
	case "cn", "xn--fiqs8s", "xn--fiqz9s":
		return prepareCN(text), true
	case "pl":
		return preparePL(text), true
	case "dk":
		return prepareDK(text), true
	case "by":
		return prepareBY(text), true
	case "ua":
		return prepareUA(text), true
	case "at":
		return prepareAT(text), true
	default:
		return text, false
	}
}

// prepareTLD do prepare the tld domain
func prepareTLD(text string) string {
	token := ""
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			token = ""
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.Split(v, ":")
			if strings.TrimSpace(vs[0]) == "organisation" {
				if token == "" {
					token = "registrant"
				}
			}
			if strings.TrimSpace(vs[0]) == "contact" {
				token = strings.TrimSpace(vs[1])
			} else {
				if token != "" {
					v = fmt.Sprintf("%s %s", token, v)
				}
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareEDU do prepare the .edu domain
func prepareEDU(text string) string {
	tokens := map[string][]string{
		"Registrant:": {
			"Organization",
			"Address",
			"Phone",
			"Email",
		},
		"Administrative Contact:": {
			"Name",
			"Organization",
			"Address",
			"Phone",
			"Email",
		},
		"Technical Contact:": {
			"Name",
			"Organization",
			"Address",
			"Phone",
			"Email",
		},
	}

	token := ""
	index := 0

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if strings.HasSuffix(v, ":") {
			token = ""
			index = 0
		}
		if _, ok := tokens[v]; ok {
			token = v
		} else {
			if token == "" {
				result += "\n" + v
			} else {
				// address ending now jump to phone
				if tokens[token][index] == "Address" && strings.HasPrefix(v, "+") {
					found := xslice.Index(tokens[token], "Phone")
					if found != -1 {
						index = found
					}
				}
				result += fmt.Sprintf("\n%s %s: %s", token[:len(token)-1], tokens[token][index], v)
				if tokens[token][index] != "Address" {
					index++
				}
			}
		}
	}

	return result
}

// prepareINT do prepare the .int domain
func prepareINT(text string) string {
	token := ""
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			token = ""
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.Split(v, ":")
			if strings.TrimSpace(vs[0]) == "organisation" {
				if token == "" {
					token = "registrant"
				}
			}
			if strings.TrimSpace(vs[0]) == "contact" {
				token = strings.TrimSpace(vs[1])
			} else {
				if token != "" {
					v = fmt.Sprintf("%s %s", token, v)
				}
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareKZ do prepare the .kz domain
func prepareKZ(text string) string {

	groupTokens := map[string]string{
		"Organization Using Domain Name": "Registrant ",
		"Administrative Contact/Agent":   "Administrative ",
	}

	topTokens := map[string]string{
		"Domain status": "Domain status : ",
	}

	tokens := map[string]string{
		"Primary server":   "name server",
		"Secondary server": "name server",
		"Current Registar": "Registrar Name",
	}

	groupToken := ""
	topToken := ""
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)

		if v == "" {
			groupToken = ""
			continue
		}

		if token, ok := groupTokens[v]; ok {
			groupToken = token
			continue
		}

		if !strings.Contains(v, ":") {
			if topToken != "" {
				v = fmt.Sprintf("%s%s", topToken, v)
			} else {
				continue
			}
		}

		vs := strings.SplitN(v, ":", 2)

		key := strings.TrimSpace(strings.Replace(vs[0], ".", "", -1))
		key = fmt.Sprintf("%s%s", groupToken, key)

		if token, ok := tokens[key]; ok {
			key = token
		}

		value := vs[1]

		if token, ok := topTokens[key]; ok {
			topToken = token
		} else {
			topToken = ""
		}

		v = fmt.Sprintf("%s: %s", key, value)

		result += "\n" + v
	}

	return result
}

// prepareMO do prepare the .mo domain
func prepareMO(text string) string {
	tokens := map[string]string{
		"Registrant:":           "Registrant",
		"Admin Contact(s):":     "Admin",
		"Billing Contact(s):":   "Billing",
		"Technical Contact(s):": "Technical",
	}

	token := ""
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			token = ""
			continue
		}
		if v[0] == '-' {
			continue
		}
		for _, s := range []string{"Record created on", "Record expires on"} {
			if strings.HasPrefix(v, s) {
				v = strings.Replace(v, s, s+":", 1)
			}
		}
		if _, ok := tokens[v]; ok {
			token = tokens[v]
		} else {
			if token != "" {
				v = fmt.Sprintf("%s %s", token, v)
			}
		}
		result += "\n" + v
	}

	return result
}

var prepareHKEmailRx = regexp.MustCompile(`Email\:\s+([^\s]+)(\s+Hotline\:(.*))?`)

// prepareHK do prepare the .hk domain
func prepareHK(text string) string {
	tokens := map[string]string{
		"Registrant Contact Information:":     "Registrant",
		"Administrative Contact Information:": "Admin",
		"Technical Contact Information:":      "Technical",
		"Name Servers Information:":           "Name Servers:",
	}

	dateTokens := []string{
		"Domain Name Commencement Date",
		"Expiry Date",
	}

	token := ""
	addressToken := false
	text = strings.Replace(text, "\n\n", "\n", -1)

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			token = ""
			continue
		}
		field := ""
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			field = strings.TrimSpace(vs[0])
			if strings.Contains(field, "(") {
				field = strings.Split(field, "(")[0]
				v = fmt.Sprintf("%s: %s", field, vs[1])
			}
			addressToken = field == "Address"
			if field == "Registrar Contact Information" {
				m := prepareHKEmailRx.FindStringSubmatch(vs[1])
				if len(m) == 4 {
					v = ""
					if m[1] != "" {
						v += "Registrar Contact Email: " + m[1] + "\n"
					}
					if m[3] != "" {
						v += "Registrar Contact Phone: " + m[3] + "\n"
					}
					v = strings.TrimSpace(v)
				}
			}
			if field == "Family name" {
				vv := strings.TrimSpace(vs[1])
				if vv != "" && vv != "." {
					result += " " + vv
				}
				continue
			}
		} else {
			if addressToken {
				result += ", " + v
				continue
			}
		}
		if _, ok := tokens[v]; ok {
			token = tokens[v]
		} else {
			if token != "" && !assert.IsContains(dateTokens, field) {
				v = fmt.Sprintf("%s %s", token, v)
			}
		}
		result += "\n" + v
	}

	return result
}

var prepareTWEmailRx = regexp.MustCompile(`(.*)\s+([^\s]+@[^\s]+)`)

// prepareTW do prepare the .tw domain
func prepareTW(text string) string { //nolint:cyclop
	tokens := map[string][]string{
		"Registrant:": {
			"Organization",
			"Organization",
			"Name,Email",
			"Phone",
			"Fax",
			"Address",
			"Address",
			"Address",
		},
		"Administrative Contact:": {
			"Name,Email",
			"Phone",
			"Fax",
		},
		"Technical Contact:": {
			"Name,Email",
			"Phone",
			"Fax",
		},
		"Contact:": {
			"Name",
			"Email",
		},
	}

	token := ""
	index := -1

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if token == "" && v == "" {
			continue
		}
		for _, s := range []string{"Record created on", "Record expires on"} {
			if strings.HasPrefix(v, s) {
				v = strings.Replace(v, s, s+":", 1)
			}
		}
		if strings.Contains(v, ":") {
			token = ""
			index = -1
		}
		if _, ok := tokens[v]; ok {
			token = v
		} else {
			if token == "" {
				result += "\n" + v
			} else {
				index++
				if index > len(tokens[token])-1 {
					continue
				}
				tokenName := token[:len(token)-1]
				// Organization may be one line or two lines
				if tokenName == "Registrant" && tokens[token][index] == "Organization" {
					if strings.Contains(v, "@") {
						// Organization one line, jump to next
						index++
					} else if index == 1 {
						// Organization two line, join it
						result = strings.TrimSpace(result)
						if !strings.HasSuffix(result, ":") {
							result += ", " + v
						} else {
							result += " " + v
						}
						continue
					}
				}
				// See testdata/noterror/tw_git.tw
				if tokenName == "Registrant" && tokens[token][index] != "Address" {
					if len(v) == 2 && strings.ToLower(v) != v {
						index = xslice.Index(tokens[token], "Address")
					}
				}
				indexName := tokens[token][index]
				if tokenName == "Contact" {
					tokenName = "Registrant Contact"
				}
				if strings.Contains(indexName, ",") {
					ins := strings.Split(indexName, ",")
					m := prepareTWEmailRx.FindStringSubmatch(v)
					if len(m) == 3 {
						result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[0], strings.TrimSpace(m[1]))
						result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[1], strings.TrimSpace(m[2]))
					} else {
						result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[0], strings.TrimSpace(v))
					}
					continue
				}
				result += fmt.Sprintf("\n%s %s: %s", tokenName, indexName, v)
			}
		}
	}

	return result
}

// prepareCH do prepare the .ch domain
func prepareCH(text string) string {
	phoneMark := "Phone +"

	tokens := map[string][]string{
		"Domain name":             {},
		"Registrar":               {"Registrar name", "Registrar street", "Registrar Phone", "Registrar Email"},
		"DNSSEC":                  {},
		"Name servers":            {},
		"First registration date": {},
	}

	var result string
	var lastToken string
	var lastTokenIndex int

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		for t := range tokens {
			if strings.HasPrefix(strings.ToLower(v)+" ", strings.ToLower(t+" ")) {
				lastToken = t
				lastTokenIndex = 0
				v = strings.TrimSpace(v[len(t):])
				break
			}
		}
		if v == "" {
			continue
		}
		if len(tokens[lastToken]) > 0 {
			if strings.HasPrefix(v, phoneMark) {
				lastTokenIndex++
				v = strings.TrimSpace(v[len(phoneMark)-1:])
			}
			result += fmt.Sprintf("\n%s: %s", tokens[lastToken][lastTokenIndex], v)
			if tokens[lastToken][lastTokenIndex] != "Registrar street" {
				lastTokenIndex++
			}
		} else {
			result += fmt.Sprintf("\n%s: %s", lastToken, v)
		}
	}

	return result
}

// prepareIT do prepare the .it domain
func prepareIT(text string) string {
	topTokens := []string{
		"Registrant",
		"Admin Contact",
		"Technical Contacts",
		"Registrar",
		"Nameservers",
	}

	topToken := ""
	subToken := ""

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if assert.IsContains(topTokens, v) {
			topToken = v + " "
			subToken = ""
		} else {
			if v[0] != '*' && strings.Contains(v, ":") {
				vs := strings.Split(v, ":")
				subToken = vs[0]
			} else {
				if subToken != "" {
					result += ", " + v
					continue
				}
			}
			if topToken != "" && !strings.Contains(v, ":") {
				result += fmt.Sprintf("\n%s: %s", topToken, v)
			} else {
				result += fmt.Sprintf("\n%s%s", topToken, v)
			}
		}
	}

	return result
}

// prepareFR do prepare the .fr domain
func prepareFR(text string) string {
	dsToken := "dsl-id"
	hdlToken := "nic-hdl"
	regToken := "registrar"

	tokens := map[string]string{
		"holder-c": "holder",
		"admin-c":  "admin",
		"tech-c":   "tech",
	}

	token := ""
	newBlock := false
	hdls := map[string]string{}

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			newBlock = true
			continue
		}

		vs := strings.Split(v, ":")
		if newBlock && strings.TrimSpace(vs[0]) == regToken {
			token = regToken + " "
			v = fmt.Sprintf("name: %s", strings.TrimSpace(vs[1]))
		}

		newBlock = false
		if t, ok := tokens[strings.TrimSpace(vs[0])]; ok {
			hdls[t] = strings.TrimSpace(vs[1])
		}

		if strings.TrimSpace(vs[0]) == dsToken && strings.TrimSpace(vs[1]) != "" {
			v += "\nDNSSEC: signed"
		}

		if strings.TrimSpace(vs[0]) == hdlToken {
			for _, kk := range keys(hdls) {
				if strings.TrimSpace(vs[1]) == hdls[kk] {
					token = kk + " "
					delete(hdls, kk)
					break
				}
			}
		}

		result += fmt.Sprintf("\n%s%s", token, v)
	}

	return result
}

// prepareRU do prepare the .ru domain
func prepareRU(text string) string {
	tokens := map[string]string{
		"person": "Registrant Name",
		"e-mail": "Registrant Email",
		"org":    "Registrant Organization",
	}

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if !strings.Contains(v, ":") {
			continue
		}
		vs := strings.Split(v, ":")
		if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
			v = fmt.Sprintf("%s: %s", vv, vs[1])
		} else if vs[0] == "nserver" {
			v = strings.Replace(v, ",", " ", -1)
		}
		result += v + "\n"
	}

	return result
}

var prepareJPreplacerRx = regexp.MustCompile(`\n(?:\w+\.\s)?\[(.+?)\][\ ]*(.+?)?`)

// prepareJP do prepare the .jp domain
func prepareJP(text string) string {
	text = prepareJPreplacerRx.ReplaceAllString(text, "\n$1: $2")

	adminToken := "Contact Information"
	addressToken := "Postal Address"

	token := ""
	prefixToken := ""

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			token = strings.TrimSpace(vs[0])
			if token == adminToken {
				prefixToken = "admin "
			}
			if strings.ToLower(token) == "registrant" {
				v = fmt.Sprintf("registrant name: %s", vs[1])
			}
			v = prepareSecondLevelJP(v, token, vs[1])
		} else {
			if token == addressToken {
				result += ", " + v
				continue
			}
		}
		result += "\n" + prefixToken + v
	}

	return result
}

// prepareJP prepares specific mappings for second level .jp domains
// examples include:
// - co.jp
// - ac.jp
// - go.jp
// - or.jp
// - ad.jp
// - ne.jp
// - gr.jp
// - ed.jp
func prepareSecondLevelJP(original string, token string, value string) string {
	if strings.ToLower(token) == "administrative contact" {
		return fmt.Sprintf("Administrative Contact ID: %s", strings.TrimSpace(value))
	}
	if strings.ToLower(token) == "technical contact" {
		return fmt.Sprintf("Technical Contact ID: %s", strings.TrimSpace(value))
	}
	if strings.ToLower(token) == "organization" || strings.ToLower(token) == "network service name" {
		return fmt.Sprintf("Registrant Organization: %s", strings.TrimSpace(value))
	}
	return original
}

// prepareUK do prepare the .uk domain
func prepareUK(text string) string {
	tokens := map[string]string{
		"URL": "Registrar URL",
	}

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
				v = fmt.Sprintf("%s: %s", vv, vs[1])
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareKR do prepare the .kr domain
func prepareKR(text string) string {
	english := "# ENGLISH"
	tokens := map[string]string{
		"Administrative Contact(AC)": "Administrative Contact Name",
		"AC E-Mail":                  "Administrative Contact E-Mail",
		"AC Phone Number":            "Administrative Contact Phone Number",
		"Authorized Agency":          "Registrar Name",
		"Registrant":                 "Registrant Name",
	}

	pos := strings.Index(text, english)
	if pos != -1 {
		text = text[pos+len(english):]
	}

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if v[0] == '\'' || v[0] == '-' {
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
				v = fmt.Sprintf("%s: %s", vv, vs[1])
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareNZ do prepare the .nz domain
func prepareNZ(text string) string {
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if strings.HasPrefix(strings.TrimSpace(vs[0]), "ns_name_") {
				v = fmt.Sprintf("name server: %s", vs[1])
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareTK do prepare the .tk domain
func prepareTK(text string) string {
	tokens := map[string]string{
		"Domain name:":        "Domain",
		"Domain Nameservers:": "Nameservers",
		"Owner contact:":      "Registrant",
		"Admin contact:":      "Admin",
		"Billing contact:":    "Billing",
		"Tech contact:":       "Technical",
		"Organisation:":       "Registrant",
	}

	fields := map[string][]string{
		"Registrant": {
			"Organization",
			"Name",
			"Address",
			"Address",
			"Address",
			"Country",
		},
	}

	token := ""
	result := ""
	index := 0

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			token = ""
			continue
		}
		if _, ok := tokens[v]; ok {
			token = tokens[v]
			continue
		}
		if token == "Domain" && strings.Contains(v, " is ") {
			vv := strings.Split(v, " is ")
			v = fmt.Sprintf("Name: %s\nStatus: %s", vv[0], vv[1])
		} else if token == "Registrant" && !strings.Contains(v, ":") {
			v = fmt.Sprintf("%s: %s", fields[token][index], v)
			index++
		}
		if token != "" {
			if !strings.Contains(v, ":") {
				v = fmt.Sprintf("%s: %s", token, v)
			} else {
				v = fmt.Sprintf("%s %s", token, v)
			}
		}
		result += "\n" + strings.TrimSpace(v)
	}

	return result
}

// prepareNL do prepare the .nl domain
func prepareNL(text string) string {
	tokens := map[string][]string{
		"Reseller:": {
			"Name",
			"Address",
			"Address",
			"Address",
			"Address",
		},
		"Registrar:": {
			"Name",
			"Address",
			"Address",
			"Address",
			"Address",
		},
	}

	token := ""
	index := 0

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if strings.HasSuffix(v, ":") {
			token = ""
			index = 0
		}
		if _, ok := tokens[v]; ok {
			token = v
		} else {
			if token == "" {
				result += "\n" + v
			} else {
				result += fmt.Sprintf("\n%s %s: %s", token[:len(token)-1], tokens[token][index], v)
				index++
			}
		}
	}

	return result
}

// prepareEU do prepare the .eu domain
func prepareEU(text string) string {
	tokens := map[string]string{
		"Registrant:":   "Registrant",
		"Technical:":    "Technical",
		"Registrar:":    "Registrar",
		"Onsite(s):":    "Onsite",
		"Name servers:": "Name servers",
	}

	token := ""
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			token = ""
			continue
		}
		if _, ok := tokens[v]; ok {
			token = tokens[v]
			continue
		}
		if token != "" {
			if strings.Contains(v, ":") {
				v = fmt.Sprintf("%s %s", token, v)
			} else {
				if strings.HasPrefix(v, "Visit www.eurid.eu") {
					continue
				}
				v = fmt.Sprintf("%s: %s", token, v)
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareBR do prepare the .br domain
func prepareBR(text string) string {
	hdlToken := "nic-hdl-br"
	tokens := map[string]string{
		"owner-c":   "registrant",
		"admin-c":   "admin",
		"tech-c":    "tech",
		"billing-c": "billing",
	}

	token := ""
	hdlMap := map[string]string{}

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			token = ""
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if strings.TrimSpace(vs[0]) == hdlToken {
				token = strings.TrimSpace(vs[1])
				hdlMap[token] = ""
			}
		}
		if token != "" {
			hdlMap[token] += "\n" + v
		}
	}

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if strings.TrimSpace(vs[0]) == "owner" {
				v = fmt.Sprintf("registrant organization: %s", vs[1])
			}
			if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
				for _, tt := range strings.Split(hdlMap[strings.TrimSpace(vs[1])], "\n") {
					if strings.TrimSpace(tt) == "" {
						continue
					}
					result += fmt.Sprintf("\n%s %s", vv, tt)
				}
				continue
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareIR do prepare the .ir domain
func prepareIR(text string) string {
	hdlToken := "nic-hdl"
	tokens := map[string]string{
		"holder-c": "registrant",
		"admin-c":  "admin",
		"tech-c":   "tech",
		"bill-c":   "billing",
	}

	token := ""
	hdlMap := map[string]string{}

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			token = ""
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if strings.TrimSpace(vs[0]) == hdlToken {
				token = strings.TrimSpace(vs[1])
				hdlMap[token] = ""
			}
		}
		if token != "" {
			hdlMap[token] += "\n" + v
		}
	}

	result := ""
	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
				for _, tt := range strings.Split(hdlMap[strings.TrimSpace(vs[1])], "\n") {
					if strings.TrimSpace(tt) == "" {
						continue
					}
					result += fmt.Sprintf("\n%s %s", vv, tt)
				}
				continue
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareFI do prepare the .fi domain
func prepareFI(text string) string {
	tokens := map[string]string{
		"Holder":    "Registrant",
		"Registrar": "Registrar",
		"Tech":      "Technical",
	}

	token := ""
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if len(v) == 0 {
			continue
		}
		if v[0] == '>' {
			token = ""
		}
		if _, ok := tokens[v]; ok {
			token = tokens[v]
		} else {
			if strings.Contains(v, ":") {
				vv := strings.SplitN(v, ":", 2)
				vv[0] = strings.Trim(vv[0], ".")
				if token == "Registrar" && vv[0] == "registrar" {
					vv[0] = "name"
				}
				v = fmt.Sprintf("%s: %s", vv[0], vv[1])
			}
			if token != "" {
				v = fmt.Sprintf("%s %s", token, v)
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareRS do prepare the .rs domain
func prepareRS(text string) string {
	tokens := map[string]string{
		"Registrant":             "Registrant",
		"Administrative contact": "Administrative",
		"Technical contact":      "Technical",
	}

	token := ""
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if len(v) == 0 {
			token = ""
			continue
		}
		if strings.Contains(v, ":") {
			vv := strings.SplitN(v, ":", 2)
			vv[0] = strings.TrimSpace(vv[0])
			if t, ok := tokens[vv[0]]; ok {
				token = t
			} else if token != "" {
				v = fmt.Sprintf("%s %s", token, v)
			}
		}
		result += "\n" + v
	}

	return result
}

// prepareEE do prepare the .ee domain
func prepareEE(text string) string {
	tokens := map[string]string{
		"Domain:":                 "Domain",
		"Registrar:":              "Registrar",
		"Registrant:":             "Registrant",
		"Administrative contact:": "Administrative",
		"Technical contact:":      "Technical",
		"Name servers:":           "",
	}

	token := ""
	result := ""

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if len(v) == 0 {
			token = ""
			continue
		}
		if t, ok := tokens[v]; ok {
			token = t
			continue
		}
		v = fmt.Sprintf("%s %s", token, v)
		result += "\n" + strings.TrimSpace(v)
	}

	return result
}

// prepareCN do prepare the .cn domain
func prepareCN(text string) string {
	var result string

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if strings.ToLower(strings.TrimSpace(vs[0])) == "registrant" {
				vs[0] = "registrant name"
			}
			v = fmt.Sprintf("%s: %s", vs[0], vs[1])
		}
		result += "\n" + v
	}

	return result
}

// preparePL prepares the .pl domain
func preparePL(text string) string {
	result := ""
	special := ""
	registrarLine := 0
	for _, v := range strings.Split(text, "\n") {
		if special == "nameservers" {
			if strings.HasPrefix(v, " ") {
				ns := strings.SplitN(v, "[", 2)
				result += fmt.Sprintf("\nnameservers: %s", strings.TrimSpace(ns[0]))
				continue
			}
			special = ""
		} else if special == "REGISTRAR" {
			if strings.TrimSpace(v) == "" {
				special = ""
			} else {
				switch registrarLine {
				case 0:
					// always name
					result += fmt.Sprintf("\nregistrar name: %s", strings.TrimSpace(v))
				case 1:
					// always street address
					result += fmt.Sprintf("\nregistrar street: %s", strings.TrimSpace(v))
				case 2:
					// postal code, city, state, sometimes country in an undefined format
					// there's no way we can reliably unpack that
					registrarLine++
					continue
				default:
					// usually country unless it was on previous line, then phones/emails/www
					if strings.Contains(v, "@") {
						// email may have an "e-mail" prefix, but mostly does not
						result += fmt.Sprintf("\nregistrar email: %s", strings.TrimSpace(strings.TrimLeft(v, "e-mail:")))
					} else if strings.HasPrefix(v, "+") {
						// phone numbers helpfully always start with +
						result += fmt.Sprintf("\nregistrar phone: %s", strings.TrimSpace(v))
					} else if strings.Contains(v, ".") {
						// WWW addresses sometimes include http/https, sometimes do not
						result += fmt.Sprintf("\nregistrar www: %s", strings.TrimSpace(v))
					} else {
						result += fmt.Sprintf("\nregistrar country: %s", strings.TrimSpace(v))
					}
				}
				registrarLine++
				continue
			}
		}

		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}

		if strings.HasPrefix(v, "nameservers: ") {
			special = "nameservers"
			ns := strings.SplitN(v, "[", 2)
			result += fmt.Sprintf("\n%s", strings.TrimSpace(ns[0]))
			continue
		}

		if strings.HasPrefix(v, "REGISTRAR:") {
			special = "REGISTRAR"
			continue
		}

		result += fmt.Sprintf("\n%s", strings.ReplaceAll(v, "WHOIS database responses:", "whois:"))
	}

	return result
}

// prepareDK do prepare the .dk domain
func prepareDK(text string) string {
	var result string

	for _, v := range strings.Split(text, "\n") {
		if strings.HasPrefix(v, "DNS:") {
			continue
		}
		result += v + "\n"
	}

	return result
}

// prepareBY do prepare the .by domain
func prepareBY(text string) string {
	var result string

	tokens := map[string]string{
		"Person":  "Registrant Person",
		"Org":     "Registrant Org",
		"Country": "Registrant Country",
		"Address": "Registrant Address",
		"Phone":   "Registrant Phone",
		"Email":   "Registrant Email",
	}

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if strings.Contains(v, ":") {
			vs := strings.SplitN(v, ":", 2)
			if t, ok := tokens[strings.TrimSpace(vs[0])]; ok {
				v = fmt.Sprintf("%s: %s", t, vs[1])
			}
		}
		result += v + "\n"
	}

	return result
}

// prepareUA do prepare the .ua domain
func prepareUA(text string) string {
	var result string

	tokens := map[string]string{
		"% Registrar:":               "Registrar",
		"% Registrant:":              "Registrant",
		"% Administrative Contacts:": "Administrative",
		"% Technical Contacts:":      "Technical",
	}

	var token string
	uniqueLine := map[string]bool{}

	for _, v := range strings.Split(text, "\n") {
		v = strings.TrimSpace(v)
		if v, ok := tokens[v]; ok {
			token = v
			continue
		}
		if token != "" && v != "" && !strings.HasPrefix(v, "%") {
			vs := strings.SplitN(v, ":", 2)
			vs[0] = strings.TrimSuffix(strings.TrimSpace(vs[0]), "-loc")
			if vs[0] == "registrar" {
				vs[0] = "name"
			}
			vs[1] = strings.TrimSpace(vs[1])
			if vs[1] == "n/a" {
				continue
			}
			v = fmt.Sprintf("%s %s", token, strings.Join(vs, ":"))
		}
		if v != "" && uniqueLine[v] {
			continue
		}
		uniqueLine[v] = true
		result += v + "\n"
	}

	return result
}

// prepareAT prepares the .at domain
func prepareAT(text string) string {
	result := ""
	registrantID := ""
	techID := ""

	tokens := map[string]string{
		"street address": "address",
		"postal code":    "address",
		"city":           "address",
		"country":        "address",
		"e-mail":         "email",
		"nic-hdl":        "id",
		"personname":     "name",
	}

	formatLine := func(line, token string) string {
		before, after, _ := strings.Cut(line, ":")
		key := strings.TrimSpace(before)
		if t, ok := tokens[key]; ok {
			key = t
		}
		val := strings.TrimSpace(after)
		return fmt.Sprintf("%s %s: %s", token, key, val)
	}

	for _, v := range strings.Split(text, "\n\n") {
		v = strings.TrimSpace(v)
		if strings.HasPrefix(v, "%") {
			continue
		}
		if strings.Contains(v, ":") {
			b := strings.Split(v, "\n")
			if strings.HasPrefix(b[0], "domain") {
				for _, l := range b {
					w := ""
					if before, after, ok := strings.Cut(l, ":"); ok {
						key := strings.TrimSpace(before)
						val := strings.TrimSpace(after)
						switch key {
						case "domain":
							w = fmt.Sprintf("%s: %s", "domain name", val)
						case "registrant":
							registrantID = val
						case "tech-c":
							techID = val
						case "changed":
							w = fmt.Sprintf("%s: %s", "updated_date", val)
						case "nserver":
							w = fmt.Sprintf("%s: %s", "name_servers", val)
						default:
							w = fmt.Sprintf("domain %s: %s", key, val)
						}
						if w != "" {
							result += w + "\n"
						}
					}
				}
			} else if strings.HasPrefix(b[0], "personname") {
				token := ""
				if strings.Contains(v, registrantID) {
					token = "registrant"
				} else if strings.Contains(v, techID) {
					token = "technical contact"
				}
				for _, l := range strings.Split(v, "\n") {
					result += formatLine(l, token) + "\n"
				}
			}
		}
	}

	return result
}
