package parser

import (
	"encoding/json"
	"fmt"
	"os"
)

type DnsServer struct {
	onlyAddr     bool
	Name         string   `json:"name,omitempty"`
	Address      string   `json:"address,omitempty"`
	ClientIP     string   `json:"clientIp,omitempty"`
	Port         uint16   `json:"port,omitempty"`
	SkipFallback bool     `json:"skipFallback,omitempty"`
	Domains      []string `json:"domains,omitempty"`
	ExpectIPs    []string `json:"expectIps,omitempty"`
}

func (d *DnsServer) UnmarshalJSON(data []byte) error {
	var addr string
	err := json.Unmarshal(data, &addr)
	if err == nil {
		d.onlyAddr = true
		d.Address = addr
		return nil
	}

	err = json.Unmarshal(data, (*_DnsServer)(d))
	if err != nil {
		return err
	}
	return nil
}

type _DnsServer DnsServer

func (d *DnsServer) MarshalJSON() ([]byte, error) {
	if d.onlyAddr {
		return json.Marshal(d.Address)
	}
	return json.Marshal((*_DnsServer)(d))
}

type DnsConfig struct {
	Hosts                  map[string]interface{} `json:"hosts,omitempty"`
	Servers                []DnsServer            `json:"servers,omitempty"`
	ClientIp               string                 `json:"clientIp,omitempty"`
	QueryStrategy          string                 `json:"queryStrategy,omitempty"`
	DisableCache           bool                   `json:"disableCache,omitempty"`
	DisableFallback        bool                   `json:"disableFallback,omitempty"`
	DisableFallbackIfMatch bool                   `json:"disableFallbackIfMatch,omitempty"`
	Tag                    string                 `json:"tag,omitempty"`
	Ver                    string                 `json:"version,omitempty"`
}

func (d DnsConfig) Version() string {
	return d.Ver
}

func (p *Parser) ParseDnsConfig(failed map[string][]string) error {
	co, err := readAndMergeConfig[DnsConfig](p.RemoteDnsPath, p.DnsPath)
	if err != nil {
		return err
	}

	var news []DnsServer
	changed := false
	for i := range co.Servers {
		if co.Servers[i].Name == "" {
			news = append(news, co.Servers[i])
		}
	}
	matchNum := 0
	for region, me := range failed {
		var addr string
		var port uint16
		if d, ok := p.Dns[region]; !ok {
			if d, ok := p.Dns["other"]; !ok {
				continue
			} else {
				addr = d.Addr
				port = d.Port
			}
		} else {
			addr = d.Addr
			port = d.Port
		}
		found := false
		ds, _ := p.getDomainAndIpRules(region, me)
		if len(ds) == 0 {
			matchNum++
			continue
		}
		for i := range co.Servers {
			if co.Servers[i].Name != region {
				continue
			}
			matchNum++
			found = true
			if isStringSliceNotEqual(co.Servers[i].Domains, ds) {
				co.Servers[i].Domains = ds
				changed = true
			}
			news = append(news, co.Servers[i])
			break
		}
		if !found {
			news = append(news, DnsServer{
				Name:    region,
				Address: addr,
				Port:    port,
				Domains: ds,
			})
			changed = true
		}
	}
	if !changed && matchNum >= len(failed) {
		return nil
	}
	co.Servers = news
	f, err := os.OpenFile(p.DnsPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0744)
	if err != nil {
		return err
	}
	defer f.Close()
	e := json.NewEncoder(f)
	e.SetIndent("", "    ")
	err = e.Encode(co)
	if err != nil {
		return fmt.Errorf("encode dns file error: %s", err)
	}
	return nil
}
