package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/adamveld12/gopherhole/internal"
	"github.com/miekg/dns"
)

var (
	configFilePath = flag.String("config", "./config.json", "Config file")
	// dbPath         = flag.String("db-path", ".", "Directory to write database files to")
	// httpAddr       = flag.String("http-address", ":8080", "Bind address for http server")
	// dnsAddr        = flag.String("dns-address", ":53", "Bind address for dns server")
)

func main() {
	flag.Parse()

	var conf StartupConfig
	if err := LoadStartupConfig(&conf, *configFilePath); err != nil {
		log.Fatalf("%+v", err)
	}
	// conf.HTTPAddr = *httpAddr
	// conf.DNSAddr = *dnsAddr
	log.Printf("%+v", conf)
	store := &internal.Sqlite{
		Path: conf.DatabaseURL,
	}

	if err := store.Open(); err != nil {
		log.Fatalf("COULD NOT OPEN SQLITE DB: %v", err)
	}

	re := &internal.RuleEngine{
		Rules: conf.Rules,
	}

	cache := &internal.Memory{}

	dnsClient := &dns.Client{
		Net:          "udp",
		DialTimeout:  time.Millisecond * 250,
		ReadTimeout:  time.Second,
		WriteTimeout: time.Second,
	}

	dm := &internal.DomainManager{
		Cache:         cache,
		Storage:       store,
		RuleEvaluator: re,
		Recursors: internal.Recursor{
			Upstreams: cleanRecursors(conf.Recursors),
			Client:    dnsClient,
		},
	}

	go func() {
		dnsSrv := &internal.DNSServer{Handler: dm}
		if err := dnsSrv.ListenAndServe(context.Background(), conf.DNSAddr); err != nil {
			log.Fatal(err)
		}
	}()

	httpApi := internal.NewAdminHandler(cache, store, re)
	if err := http.ListenAndServe(conf.HTTPAddr, httpApi); err != nil {
		log.Fatal(err)
	}
}

func cleanRecursors(recursors []string) []string {
	cr := []string{}
	reg := regexp.MustCompile(`^((?:\d{1,4}\.?){4})(?::(\d{0,5}))?`)
	for _, r := range recursors {
		if !reg.MatchString(r) {
			log.Fatalf("%s is not a valid DNS server. Must be in ip:addr format.", r)
		}

		cleanedIPAddr := r
		if !strings.Contains(r, ":") {
			cleanedIPAddr = fmt.Sprintf("%s:53", r)
		}
		cr = append(cr, cleanedIPAddr)
	}

	log.Println(cr)

	return cr
}
