package internal

import (
	"log"
	"strings"
	"time"

	"github.com/miekg/dns"
)

type DomainManager struct {
	Cache
	Storage
	RuleEvaluator
	Recursors Recursor
}

func (dm *DomainManager) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
	// only grab first question: https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
	start := time.Now()
	q := r.Question[0]
	responseMessage := new(dns.Msg)
	ql := QueryLog{
		Started:  start.UTC(),
		Protocol: w.RemoteAddr().Network(),
		ClientIP: strings.Split(w.RemoteAddr().String(), ":")[0],
		Domain:   q.Name,
		Status:   NoAnswer,
	}

	var resolved Resolved
	var err error
	// lookup in cache
	if dest := dm.LookupRecord(q.Name); dest != nil {
		responseMessage = new(dns.Msg)
		responseMessage.Answer = dest
		ql.Status = CacheHit
	} else if rule, ok := dm.Evaluate(q.Name); ok {
		// evaluate domain in rules engine
		responseMessage = rule.CreateAnswer(q.Name)
		responseMessage.Authoritative = true
		ql.Status = CustomRule
	} else if resolved, err = dm.Recursors.Resolve(r); err == nil {
		dm.SaveAnswers(q.Name, resolved.Message.Answer)
		responseMessage = resolved.Message
		ql.RecurseUpstreamIP = resolved.UpstreamUsed
		ql.RecurseRoundTripTimeMs = int(resolved.RoundtripTime.Milliseconds())
		ql.Status = RecursedUpstream
	} else if err != nil {
		ql.Error = err.Error()
	}

	responseMessage.SetReply(r)
	responseMessage.RecursionAvailable = true
	responseMessage.Compress = true

	ql.TotalTimeMs = int(time.Since(start).Milliseconds())
	log.Printf("%+v", ql)
	go func() {
		if err := dm.Storage.Log(ql); err != nil {
			log.Printf("ERROR WRITING LOG: %v", err)
		}
	}()

	if err := w.WriteMsg(responseMessage); err != nil {
		log.Println(err)
	}
}

type ResponseStatus string

const (
	CacheMiss        = ResponseStatus("CACHE MISS")
	CacheHit         = ResponseStatus("CACHE HIT")
	CustomRule       = ResponseStatus("CUSTOM RULE")
	RecursedUpstream = ResponseStatus("RECURSED")
	NoAnswer         = ResponseStatus("NO ANSWER")
)

type QueryLog struct {
	Started                time.Time
	ClientIP               string
	Protocol               string
	Domain                 string
	TotalTimeMs            int
	RecurseRoundTripTimeMs int
	RecurseUpstreamIP      string
	Error                  string
	Status                 ResponseStatus
}
