package host

import (
	"fmt"
	"net"
	"os"
	"regexp"
	"strings"

	"git.giaever.org/joachimmg/go-log.git/log"
	"git.giaever.org/joachimmg/m-dns/errors"
)

type HostString interface {
	String() string
}

type Instance interface {
	HostString
	EncodedInstance() string
}

type Service interface {
	HostString
	Types() []string
	RootType() string
}

type Domain interface {
	HostString
}

type Hostname interface {
	HostString
}

type TXT interface {
	HostString
}

type String string

const (
	EmptyString String = ""
)

func (s String) String() string {
	return string(s)
}

func (s String) isEmpty() bool {
	return len(s) == 0
}

func (s String) isValid() error {
	log.Traceln(errors.HostString, s)
	if s.isEmpty() {
		log.Traceln(errors.HostString, s, errors.HostStringIsEmpty)
		return errors.HostStringIsEmpty
	}

	if s[len(s)-1] == '.' {
		log.Traceln(errors.HostString, s, errors.HostStringIsInvalid)
		return errors.HostStringIsInvalid
	}

	return nil
}

func (s String) isInstanceVariable() (String, error) {
	if s.isEmpty() {
		log.Traceln(errors.HostString, errors.HostStringIsInvalidInstance)
		return EmptyString, errors.HostStringIsInvalidInstance
	}

	re := regexp.MustCompile(`^[\x20-\x7E]+$`)

	if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 {
		return s, nil
	}

	log.Traceln(errors.HostString, errors.HostStringIsInvalidInstance)
	return EmptyString, errors.HostStringIsInvalidInstance
}

func (s String) isServiceVariable() (String, error) {
	if err := s.isValid(); err != nil {
		log.Traceln(errors.HostString, errors.HostStringIsInvalidService, err)
		return EmptyString, errors.HostStringIsInvalidService
	}

	// RFC 6763: Service pair _<name>._<type> (including (_sub.+)._name._type)
	re := regexp.MustCompile(`^((?:(\_[a-z\-]+)\.)+)(?:(\_+(?:tcp|udp))+)$`)

	if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 {
		switch rs[0][3] {
		case "_tcp", "_udp":
			return s, nil
		}
	}

	log.Traceln(errors.HostString, errors.HostStringIsInvalidService)
	return EmptyString, errors.HostStringIsInvalidService
}

func (s String) isDomainVariable() (String, error) {
	if err := s.isValid(); err != nil {
		log.Traceln(errors.HostString, errors.HostStringIsInvalidDomain)
		return EmptyString, errors.HostStringIsInvalidDomain
	}

	if s == "local" {
		return s, nil
	}

	if _, err := net.LookupHost(s.String()); err != nil {
		log.Traceln(errors.HostString, err)
		return EmptyString, errors.HostStringIsInvalidDomain
	}

	return s, nil
}

func (s String) isHostnameVariable() (String, error) {
	if err := s.isValid(); err != nil {
		log.Traceln(errors.HostString, errors.HostStringIsInvalidHostname, err)
		return EmptyString, errors.HostStringIsInvalidHostname
	}

	if hostname, err := os.Hostname(); err == nil {
		if s.String() == hostname {
			return s, nil
		}
	}

	if _, err := net.LookupHost(s.String()); err != nil {
		log.Traceln(errors.HostString, err)
		return EmptyString, errors.HostStringIsInvalidHostname
	}

	return s, nil
}

func (s String) isTxtVariable() (String, error) {
	log.Traceln(errors.HostString, s)
	if s.isEmpty() || s[:1][0] == '=' {
		return EmptyString, nil
	}

	if len(s) > 200 {
		return EmptyString, errors.HostTXTExceedsLimit
	}

	// RFC 6763: Spaces in key is significant, can include any character(incl. '=') in value
	re := regexp.MustCompile(`^([\x20-\x3C\x3E-\x7E]+)(?:(\=+)?([\x20-\x7E]+)?)$`)

	if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 {
		return String(fmt.Sprintf("%s%s%s", strings.Trim(rs[0][1], " "), rs[0][2], rs[0][3])), nil
	}

	return EmptyString, nil
}

func (s String) EncodedInstance() string {
	// RFC 6763, 4.3: Must be escaped, except leading _
	return strings.Replace(regexp.QuoteMeta(s.String()), "_", `\_`, -1)
}

func (s String) Types() []string {
	return strings.Split(s.String(), ".")
}

func (s String) RootType() string {
	t := s.Types()
	return t[len(t)-1]
}
