package autodns

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"sort"

	"github.com/StackExchange/dnscontrol/v3/models"
)

type ZoneListFilter struct {
	Key      string            `json:"key"`
	Value    string            `json:"value"`
	Operator string            `json:"operator"`
	Link     string            `json:"link,omitempty"`
	Filter   []*ZoneListFilter `json:"filters,omitempty"`
}

type ZoneListRequest struct {
	Filter []*ZoneListFilter `json:"filters"`
}

func (api *autoDnsProvider) request(method string, requestPath string, data interface{}) ([]byte, error) {
	client := &http.Client{}

	requestUrl := api.baseURL
	requestUrl.Path = api.baseURL.Path + requestPath

	request := &http.Request{
		URL:    &requestUrl,
		Header: api.defaultHeaders,
		Method: method,
	}

	if data != nil {
		body, _ := json.Marshal(data)
		buffer := bytes.NewBuffer(body)
		request.Body = io.NopCloser(buffer)
	}

	response, error := client.Do(request)
	if error != nil {
		return nil, error
	}
	defer response.Body.Close()

	responseText, _ := ioutil.ReadAll(response.Body)
	if response.StatusCode != 200 {
		return nil, errors.New("Request to " + requestUrl.Path + " failed: " + string(responseText))
	}

	return responseText, nil
}

func (api *autoDnsProvider) findZoneSystemNameServer(domain string) (*models.Nameserver, error) {
	request := &ZoneListRequest{}

	request.Filter = append(request.Filter, &ZoneListFilter{
		Key:      "name",
		Value:    domain,
		Operator: "EQUAL",
	})

	responseData, err := api.request("POST", "zone/_search", request)
	if err != nil {
		return nil, err
	}

	var responseObject JSONResponseDataZone
	_ = json.Unmarshal(responseData, &responseObject)
	if len(responseObject.Data) != 1 {
		return nil, errors.New("Domain " + domain + " could not be found in AutoDNS")
	}

	systemNameServer := &models.Nameserver{Name: responseObject.Data[0].SystemNameServer}

	return systemNameServer, nil
}

func (api *autoDnsProvider) getZone(domain string) (*Zone, error) {
	systemNameServer, err := api.findZoneSystemNameServer(domain)
	if err != nil {
		return nil, err
	}

	// if resolving of a systemNameServer succeeds the system contains this zone
	var responseData, _ = api.request("GET", "zone/"+domain+"/"+systemNameServer.Name, nil)
	var responseObject JSONResponseDataZone
	// make sure that the response is valid, the zone is in AutoDNS but we're not sure the returned data meets our expectation
	unmErr := json.Unmarshal(responseData, &responseObject)
	if unmErr != nil {
		return nil, unmErr
	}

	return responseObject.Data[0], nil
}

func (api *autoDnsProvider) updateZone(domain string, resourceRecords []*ResourceRecord, nameServers []*models.Nameserver, zoneTTL uint32) error {
	systemNameServer, err := api.findZoneSystemNameServer(domain)

	if err != nil {
		return err
	}

	zone, _ := api.getZone(domain)

	zone.Origin = domain
	zone.SystemNameServer = systemNameServer.Name

	zone.IncludeWwwForMain = false

	zone.Soa.TTL = zoneTTL

	// empty out NameServers and ResourceRecords, add what it should be
	zone.NameServers = []*models.Nameserver{}
	zone.ResourceRecords = []*ResourceRecord{}

	zone.ResourceRecords = append(zone.ResourceRecords, resourceRecords...)

	// naive approach, the first nameserver passed should be the systemNameServer, the will be named alphabetically
	sort.Slice(nameServers, func(i, j int) bool {
		return nameServers[i].Name < nameServers[j].Name
	})

	zone.NameServers = append(zone.NameServers, nameServers...)

	var _, putErr = api.request("PUT", "zone/"+domain+"/"+systemNameServer.Name, zone)

	if putErr != nil {
		return putErr
	}

	return nil
}
