package dns

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v12/pkg/edgegriderr"
	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v12/pkg/session"
	validation "github.com/go-ozzo/ozzo-validation/v4"
)

var (
	zoneWriteLock sync.Mutex
)

type (
	// ZoneQueryString contains zone query parameters
	ZoneQueryString struct {
		Contract string
		Group    string
	}

	// ZoneCreate contains zone create request
	ZoneCreate struct {
		Zone                  string                `json:"zone"`
		Type                  string                `json:"type"`
		Masters               []string              `json:"masters,omitempty"`
		Comment               string                `json:"comment,omitempty"`
		SignAndServe          bool                  `json:"signAndServe"`
		SignAndServeAlgorithm string                `json:"signAndServeAlgorithm,omitempty"`
		TSIGKey               *TSIGKey              `json:"tsigKey,omitempty"`
		Target                string                `json:"target,omitempty"`
		EndCustomerID         string                `json:"endCustomerId,omitempty"`
		ContractID            string                `json:"contractId,omitempty"`
		OutboundZoneTransfer  *OutboundZoneTransfer `json:"outboundZoneTransfer,omitempty"`
	}

	// OutboundZoneTransfer contains OutboundZoneTransfer request parameters
	OutboundZoneTransfer struct {
		ACL           []string `json:"ACL"`
		Enabled       bool     `json:"enabled"`
		NotifyTargets []string `json:"notifyTargets"`
		TSIGKey       *TSIGKey `json:"tsigKey,omitempty"`
	}

	// ZoneResponse contains zone create response
	ZoneResponse struct {
		Zone                  string                `json:"zone,omitempty"`
		Type                  string                `json:"type,omitempty"`
		Masters               []string              `json:"masters,omitempty"`
		Comment               string                `json:"comment,omitempty"`
		SignAndServe          bool                  `json:"signAndServe"`
		SignAndServeAlgorithm string                `json:"signAndServeAlgorithm,omitempty"`
		TSIGKey               *TSIGKey              `json:"tsigKey,omitempty"`
		Target                string                `json:"target,omitempty"`
		EndCustomerID         string                `json:"endCustomerId,omitempty"`
		ContractID            string                `json:"contractId,omitempty"`
		AliasCount            int64                 `json:"aliasCount,omitempty"`
		ActivationState       string                `json:"activationState,omitempty"`
		LastActivationDate    string                `json:"lastActivationDate,omitempty"`
		LastModifiedBy        string                `json:"lastModifiedBy,omitempty"`
		LastModifiedDate      string                `json:"lastModifiedDate,omitempty"`
		VersionID             string                `json:"versionId,omitempty"`
		OutboundZoneTransfer  *OutboundZoneTransfer `json:"outboundZoneTransfer,omitempty"`
	}

	// ListMetadata contains metadata for List Zones request
	ListMetadata struct {
		ContractIDs   []string `json:"contractIds"`
		Page          int      `json:"page"`
		PageSize      int      `json:"pageSize"`
		ShowAll       bool     `json:"showAll"`
		TotalElements int      `json:"totalElements"`
	}

	// ZoneListResponse contains response for List Zones request
	ZoneListResponse struct {
		Metadata *ListMetadata  `json:"metadata,omitempty"`
		Zones    []ZoneResponse `json:"zones,omitempty"`
	}

	// ZoneRequest contains request parameters
	ZoneRequest struct {
		Zone string
	}

	// GetZoneResponse contains the response data from GetZone operation
	GetZoneResponse ZoneResponse

	// GetChangeListResponse contains metadata about a change list
	GetChangeListResponse struct {
		Zone             string `json:"zone,omitempty"`
		ChangeTag        string `json:"changeTag,omitempty"`
		ZoneVersionID    string `json:"zoneVersionId,omitempty"`
		LastModifiedDate string `json:"lastModifiedDate,omitempty"`
		Stale            bool   `json:"stale,omitempty"`
	}

	// ZoneNameListResponse contains response with a list of zone's names and aliases
	ZoneNameListResponse struct {
		Zones   []string `json:"zones"`
		Aliases []string `json:"aliases,omitempty"`
	}

	// GetZoneNamesResponse contains record set names for zone
	GetZoneNamesResponse struct {
		Names []string `json:"names"`
	}

	// GetZoneNameTypesResponse contains record set types for zone
	GetZoneNameTypesResponse struct {
		Types []string `json:"types"`
	}
	// GetZoneRequest contains request parameters for GetZone
	GetZoneRequest ZoneRequest

	// GetChangeListRequest contains request parameters for GetChangeList
	GetChangeListRequest ZoneRequest

	// ListZonesRequest contains request parameters for ListZones
	ListZonesRequest struct {
		ContractIDs string
		Page        int
		PageSize    int
		Search      string
		ShowAll     bool
		SortBy      string
		Types       string
	}
	// GetMasterZoneFileRequest contains request parameters for GetMasterZoneFile
	GetMasterZoneFileRequest ZoneRequest

	// PostMasterZoneFileRequest contains request parameters for PostMasterZoneFile
	PostMasterZoneFileRequest struct {
		Zone     string
		FileData string
	}
	// CreateZoneRequest contains request parameters for CreateZone
	CreateZoneRequest struct {
		CreateZone      *ZoneCreate
		ZoneQueryString ZoneQueryString
		ClearConn       []bool
	}
	// SaveChangeListRequest contains request parameters for SaveChangelist
	SaveChangeListRequest ZoneCreate

	// SubmitChangeListRequest contains request parameters for SubmitChangeList
	SubmitChangeListRequest ZoneCreate

	// UpdateZoneRequest contains request parameters for UpdateZone
	UpdateZoneRequest struct {
		CreateZone *ZoneCreate
	}
	// GetZoneNamesRequest contains request parameters for GetZoneNames
	GetZoneNamesRequest ZoneRequest

	// GetZoneNameTypesRequest contains request parameters for GetZoneNameTypes
	GetZoneNameTypesRequest struct {
		Zone     string
		ZoneName string
	}

	// GetZonesDNSSecStatusRequest is used to get the DNSSEC status for one or more zones
	GetZonesDNSSecStatusRequest struct {
		Zones []string `json:"zones"`
	}

	// GetZonesDNSSecStatusResponse represents a list of DNSSEC statuses for DNS zones specified
	// in the GetZonesDNSSecStatus request
	GetZonesDNSSecStatusResponse struct {
		DNSSecStatuses []SecStatus `json:"dnsSecStatuses"`
	}

	// SecStatus represents the DNSSEC status for a DNS zone
	SecStatus struct {
		Zone           string      `json:"zone"`
		Alerts         []string    `json:"alerts"`
		CurrentRecords SecRecords  `json:"currentRecords"`
		NewRecords     *SecRecords `json:"newRecords"`
	}

	// SecRecords represents a set of DNSSEC records for a DNS zone
	SecRecords struct {
		DNSKeyRecord     string    `json:"dnskeyRecord"`
		DSRecord         string    `json:"dsRecord"`
		ExpectedTTL      int64     `json:"expectedTtl"`
		LastModifiedDate time.Time `json:"lastModifiedDate"`
	}
)

var (
	// ErrGetZone is returned when GetZone fails
	ErrGetZone = errors.New("get zone")
	// ErrGetChangeList is returned when GetChangeList fails
	ErrGetChangeList = errors.New("get change list")
	// ErrGetMasterZoneFile is returned when GetMasterZoneFile fails
	ErrGetMasterZoneFile = errors.New("get master zone file")
	// ErrPostMasterZoneFile is returned when PostMasterZoneFile fails
	ErrPostMasterZoneFile = errors.New("post master zone file")
	// ErrCreateZone is returned when CreateZone fails
	ErrCreateZone = errors.New("create zone")
	// ErrSaveChangeList is returned when SaveChangeList fails
	ErrSaveChangeList = errors.New("save change list")
	// ErrSubmitChangeList is returned when SubmitChangeList fails
	ErrSubmitChangeList = errors.New("submit change list")
	// ErrGetZoneNames is returned when GetZoneNames fails
	ErrGetZoneNames = errors.New("get zone names")
	// ErrGetZoneNameTypes is returned when GetZoneNameTypes fails
	ErrGetZoneNameTypes = errors.New("get zone name types")
)

// Validate validates GetZoneNameTypesRequest
func (r GetZoneNameTypesRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zone":     validation.Validate(r.Zone, validation.Required),
		"ZoneName": validation.Validate(r.ZoneName, validation.Required),
	})
}

// Validate validates GetZoneNamesRequest
func (r GetZoneNamesRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zone": validation.Validate(r.Zone, validation.Required),
	})
}

// Validate validates SubmitChangeListRequest
func (r SubmitChangeListRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zone": validation.Validate(r.Zone, validation.Required),
	})
}

// Validate validates SaveChangelistRequest
func (r SaveChangeListRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zone": validation.Validate(r.Zone, validation.Required),
	})
}

// Validate validates PostMasterZoneFileRequest
func (r PostMasterZoneFileRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zone": validation.Validate(r.Zone, validation.Required),
	})
}

// Validate validates CreateZoneRequest
func (r CreateZoneRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"ZoneQueryString": validation.Validate(r.ZoneQueryString, validation.Required),
	})
}

// Validate validates GetZoneRequest
func (r GetZoneRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zone": validation.Validate(r.Zone, validation.Required),
	})
}

// Validate validates GetMasterZoneFileRequest
func (r GetMasterZoneFileRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zone": validation.Validate(r.Zone, validation.Required),
	})
}

// Validate validates GetChangeListRequest
func (r GetChangeListRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zone": validation.Validate(r.Zone, validation.Required),
	})
}

// Validate validates GetZonesDNSSecStatusRequest
func (r GetZonesDNSSecStatusRequest) Validate() error {
	return edgegriderr.ParseValidationErrors(validation.Errors{
		"Zones": validation.Validate(r.Zones, validation.Required),
	})
}

var zoneStructMap = map[string]string{
	"Zone":                  "zone",
	"Type":                  "type",
	"Masters":               "masters",
	"Comment":               "comment",
	"SignAndServe":          "signAndServe",
	"SignAndServeAlgorithm": "signAndServeAlgorithm",
	"TSIGKey":               "tsigKey",
	"Target":                "target",
	"EndCustomerID":         "endCustomerId",
	"OutboundZoneTransfer":  "outboundZoneTransfer",
	"ContractID":            "contractId"}

// Util to convert struct to http request body, e.g. io.reader
func convertStructToReqBody(srcStruct interface{}) (io.Reader, error) {
	reqBody, err := json.Marshal(srcStruct)
	if err != nil {
		return nil, err
	}
	return bytes.NewBuffer(reqBody), nil
}

func (d *dns) ListZones(ctx context.Context, params ListZonesRequest) (*ZoneListResponse, error) {
	logger := d.Log(ctx)
	logger.Debug("ListZones")

	getURL := "/config-dns/v2/zones"

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create listzones request: %w", err)
	}

	q := req.URL.Query()
	if params.Page > 0 {
		q.Add("page", strconv.Itoa(params.Page))
	}
	if params.PageSize > 0 {
		q.Add("pageSize", strconv.Itoa(params.PageSize))
	}
	if params.Search != "" {
		q.Add("search", params.Search)
	}
	q.Add("showAll", strconv.FormatBool(params.ShowAll))
	if params.SortBy != "" {
		q.Add("sortBy", params.SortBy)
	}
	if params.Types != "" {
		q.Add("types", params.Types)
	}
	if params.ContractIDs != "" {
		q.Add("contractIds", params.ContractIDs)
	}
	req.URL.RawQuery = q.Encode()

	var result ZoneListResponse
	resp, err := d.Exec(req, &result)
	if err != nil {
		return nil, fmt.Errorf("listzones request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, d.Error(resp)
	}

	return &result, nil
}

func (d *dns) GetZone(ctx context.Context, params GetZoneRequest) (*GetZoneResponse, error) {
	logger := d.Log(ctx)
	logger.Debug("GetZone")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%s: %w: %s", ErrGetZone, ErrStructValidation, err)
	}

	getURL := fmt.Sprintf("/config-dns/v2/zones/%s", params.Zone)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create GetZone request: %w", err)
	}

	var result GetZoneResponse
	resp, err := d.Exec(req, &result)
	if err != nil {
		return nil, fmt.Errorf("GetZone request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, d.Error(resp)
	}

	return &result, nil
}

func (d *dns) GetChangeList(ctx context.Context, params GetChangeListRequest) (*GetChangeListResponse, error) {
	logger := d.Log(ctx)
	logger.Debug("GetChangeList")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%s: %w: %s", ErrGetChangeList, ErrStructValidation, err)
	}

	getURL := fmt.Sprintf("/config-dns/v2/changelists/%s", params.Zone)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create GetChangeList request: %w", err)
	}

	var result GetChangeListResponse
	resp, err := d.Exec(req, &result)
	if err != nil {
		return nil, fmt.Errorf("GetChangeList request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, d.Error(resp)
	}

	return &result, nil
}

func (d *dns) GetMasterZoneFile(ctx context.Context, params GetMasterZoneFileRequest) (string, error) {
	logger := d.Log(ctx)
	logger.Debug("GetMasterZoneFile")

	if err := params.Validate(); err != nil {
		return "", fmt.Errorf("%s: %w: %s", ErrGetMasterZoneFile, ErrStructValidation, err)
	}

	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/zone-file", params.Zone)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
	if err != nil {
		return "", fmt.Errorf("failed to create GetMasterZoneFile request: %w", err)
	}
	req.Header.Add("Accept", "text/dns")

	resp, err := d.Exec(req, nil)
	if err != nil {
		return "", fmt.Errorf("GetMasterZoneFile request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return "", d.Error(resp)
	}

	masterFile, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("GetMasterZoneFile request failed: %w", err)
	}

	return string(masterFile), nil
}

func (d *dns) PostMasterZoneFile(ctx context.Context, params PostMasterZoneFileRequest) error {
	logger := d.Log(ctx)
	logger.Debug("PostMasterZoneFile")

	if err := params.Validate(); err != nil {
		return fmt.Errorf("%s: %w: %s", ErrPostMasterZoneFile, ErrStructValidation, err)
	}

	mtResp := ""
	pmzfURL := fmt.Sprintf("/config-dns/v2/zones/%s/zone-file", params.Zone)
	buf := bytes.NewReader([]byte(params.FileData))
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, pmzfURL, buf)
	if err != nil {
		return fmt.Errorf("failed to create PostMasterZoneFile request: %w", err)
	}

	req.Header.Set("Content-Type", "text/dns")

	resp, err := d.Exec(req, &mtResp)
	if err != nil {
		return fmt.Errorf("Create PostMasterZoneFile failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusNoContent {
		return d.Error(resp)
	}

	return nil
}

func (d *dns) CreateZone(ctx context.Context, params CreateZoneRequest) error {
	// This lock will restrict the concurrency of API calls
	// to 1 save request at a time. This is needed for the Soa.Serial value which
	// is required to be incremented for every subsequent update to a zone,
	// so we have to save just one request at a time to ensure this is always
	// incremented properly

	zoneWriteLock.Lock()
	defer zoneWriteLock.Unlock()

	logger := d.Log(ctx)
	logger.Debug("Zone Create")

	if err := params.Validate(); err != nil {
		return fmt.Errorf("%s: %w: %s", ErrCreateZone, ErrStructValidation, err)
	}

	if err := ValidateZone(params.CreateZone); err != nil {
		return err
	}

	uri, err := url.Parse("/config-dns/v2/zones")
	if err != nil {
		return fmt.Errorf("%w: failed to parse uri: %s", ErrCreateZone, err)
	}

	q := uri.Query()
	q.Add("contractId", params.ZoneQueryString.Contract)
	if params.ZoneQueryString.Group != "" {
		q.Add("gid", params.ZoneQueryString.Group)
	}
	uri.RawQuery = q.Encode()

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil)
	if err != nil {
		return fmt.Errorf("failed to create zone create request: %w", err)
	}

	zoneMap := filterZoneCreate(params.CreateZone)
	var zoneResponse ZoneResponse
	resp, err := d.Exec(req, &zoneResponse, zoneMap)
	if err != nil {
		return fmt.Errorf("create zone request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusCreated {
		return d.Error(resp)
	}

	if strings.ToUpper(params.CreateZone.Type) == "PRIMARY" {
		// Timing issue with Create immediately followed by SaveChangelist
		for _, shouldClear := range params.ClearConn {
			// should only be one entry
			if shouldClear {
				logger.Info("Clearing Idle Connections")
				d.Client().CloseIdleConnections()
			}
		}
	}

	return nil
}

func (d *dns) SaveChangeList(ctx context.Context, params SaveChangeListRequest) error {
	// This lock will restrict the concurrency of API calls
	// to 1 save request at a time. This is needed for the Soa.Serial value which
	// is required to be incremented for every subsequent update to a zone
	// so we have to save just one request at a time to ensure this is always
	// incremented properly

	zoneWriteLock.Lock()
	defer zoneWriteLock.Unlock()

	logger := d.Log(ctx)
	logger.Debug("SaveChangeList")

	if err := params.Validate(); err != nil {
		return fmt.Errorf("%s: %w: %s", ErrSaveChangeList, ErrStructValidation, err)
	}

	reqBody, err := convertStructToReqBody("")
	if err != nil {
		return fmt.Errorf("failed to generate request body: %w", err)
	}

	postURL := fmt.Sprintf("/config-dns/v2/changelists?zone=%s", params.Zone)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody)
	if err != nil {
		return fmt.Errorf("failed to create SaveChangeList request: %w", err)
	}

	resp, err := d.Exec(req, nil)
	if err != nil {
		return fmt.Errorf("SaveChangeList request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusCreated {
		return d.Error(resp)
	}

	return nil
}

func (d *dns) SubmitChangeList(ctx context.Context, params SubmitChangeListRequest) error {
	// This lock will restrict the concurrency of API calls
	// to 1 save request at a time. This is needed for the Soa.Serial value which
	// is required to be incremented for every subsequent update to a zone
	// so we have to save just one request at a time to ensure this is always
	// incremented properly

	zoneWriteLock.Lock()
	defer zoneWriteLock.Unlock()

	logger := d.Log(ctx)
	logger.Debug("SubmitChangeList")

	if err := params.Validate(); err != nil {
		return fmt.Errorf("%s: %w: %s", ErrSubmitChangeList, ErrStructValidation, err)
	}

	reqBody, err := convertStructToReqBody("")
	if err != nil {
		return fmt.Errorf("failed to generate request body: %w", err)
	}

	postURL := fmt.Sprintf("/config-dns/v2/changelists/%s/submit", params.Zone)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody)
	if err != nil {
		return fmt.Errorf("failed to create SubmitChangeList request: %w", err)
	}

	resp, err := d.Exec(req, nil)
	if err != nil {
		return fmt.Errorf("SubmitChangeList request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusNoContent {
		return d.Error(resp)
	}

	return nil
}

func (d *dns) UpdateZone(ctx context.Context, params UpdateZoneRequest) error {
	// This lock will restrict the concurrency of API calls
	// to 1 save request at a time. This is needed for the Soa.Serial value which
	// is required to be incremented for every subsequent update to a zone
	// so we have to save just one request at a time to ensure this is always
	// incremented properly

	zoneWriteLock.Lock()
	defer zoneWriteLock.Unlock()

	logger := d.Log(ctx)
	logger.Debug("Zone Update")

	if err := ValidateZone(params.CreateZone); err != nil {
		return err
	}

	zoneMap := filterZoneCreate(params.CreateZone)

	putURL := fmt.Sprintf("/config-dns/v2/zones/%s", params.CreateZone.Zone)
	req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil)
	if err != nil {
		return fmt.Errorf("failed to create Get Update request: %w", err)
	}

	var result ZoneResponse
	resp, err := d.Exec(req, &result, zoneMap)
	if err != nil {
		return fmt.Errorf("zone update request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return d.Error(resp)
	}

	return nil
}

func filterZoneCreate(zone *ZoneCreate) map[string]interface{} {
	zoneType := strings.ToUpper(zone.Type)
	filteredZone := make(map[string]interface{})
	zoneElems := reflect.ValueOf(zone).Elem()
	for i := 0; i < zoneElems.NumField(); i++ {
		varName := zoneElems.Type().Field(i).Name
		varLower := zoneStructMap[varName]
		varValue := zoneElems.Field(i).Interface()
		switch varName {
		case "Target":
			if zoneType == "ALIAS" {
				filteredZone[varLower] = varValue
			}
		case "TSIGKey":
			if zoneType == "SECONDARY" {
				filteredZone[varLower] = varValue
			}
		case "Masters":
			if zoneType == "SECONDARY" {
				filteredZone[varLower] = varValue
			}
		case "SignAndServe":
			if zoneType != "ALIAS" {
				filteredZone[varLower] = varValue
			}
		case "SignAndServeAlgorithm":
			if zoneType != "ALIAS" {
				filteredZone[varLower] = varValue
			}
		case "OutboundZoneTransfer":
			// this is workaround for the check if value is not nil to avoid adding empty entry
			switch v := varValue.(type) {
			case *OutboundZoneTransfer:
				{
					if v != nil {
						filteredZone[varLower] = varValue
					}
				}
			default:
				filteredZone[varLower] = varValue
			}
		default:
			filteredZone[varLower] = varValue
		}
	}

	return filteredZone
}

// ValidateZone validates ZoneCreate Object
func ValidateZone(zone *ZoneCreate) error {
	if len(zone.Zone) == 0 {
		return fmt.Errorf("Zone name is required")
	}
	zType := strings.ToUpper(zone.Type)
	if zType != "PRIMARY" && zType != "SECONDARY" && zType != "ALIAS" {
		return fmt.Errorf("Invalid zone type")
	}
	if zType != "SECONDARY" && zone.TSIGKey != nil {
		return fmt.Errorf("TsigKey is invalid for %s zone type", zType)
	}
	if zType == "ALIAS" {
		if len(zone.Target) == 0 {
			return fmt.Errorf("Target is required for Alias zone type")
		}
		if len(zone.Masters) > 0 {
			return fmt.Errorf("Masters is invalid for Alias zone type")
		}
		if zone.SignAndServe {
			return fmt.Errorf("SignAndServe is invalid for Alias zone type")
		}
		if len(zone.SignAndServeAlgorithm) > 0 {
			return fmt.Errorf("SignAndServeAlgorithm is invalid for Alias zone type")
		}
		return nil
	}
	// Primary or Secondary
	if len(zone.Target) > 0 {
		return fmt.Errorf("Target is invalid for %s zone type", zType)
	}
	if len(zone.Masters) > 0 && zType == "PRIMARY" {
		return fmt.Errorf("Masters is invalid for Primary zone type")
	}

	return nil
}

func (d *dns) GetZoneNames(ctx context.Context, params GetZoneNamesRequest) (*GetZoneNamesResponse, error) {
	logger := d.Log(ctx)
	logger.Debug("GetZoneNames")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%s: %w: %s", ErrGetZoneNames, ErrStructValidation, err)
	}

	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names", params.Zone)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create GetZoneNames request: %w", err)
	}

	var result GetZoneNamesResponse
	resp, err := d.Exec(req, &result)
	if err != nil {
		return nil, fmt.Errorf("GetZoneNames request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, d.Error(resp)
	}

	return &result, nil
}

func (d *dns) GetZoneNameTypes(ctx context.Context, params GetZoneNameTypesRequest) (*GetZoneNameTypesResponse, error) {
	logger := d.Log(ctx)
	logger.Debug(" GetZoneNameTypes")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%s: %w: %s", ErrGetZoneNameTypes, ErrStructValidation, err)
	}

	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types", params.Zone, params.ZoneName)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create GetZoneNameTypes request: %w", err)
	}

	var result GetZoneNameTypesResponse
	resp, err := d.Exec(req, &result)
	if err != nil {
		return nil, fmt.Errorf("GetZoneNameTypes request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, d.Error(resp)
	}

	return &result, nil
}

func (d *dns) GetZonesDNSSecStatus(ctx context.Context, params GetZonesDNSSecStatusRequest) (*GetZonesDNSSecStatusResponse, error) {
	logger := d.Log(ctx)
	logger.Debug("GetZonesDNSSecStatus")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%w: %s", ErrStructValidation, err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/config-dns/v2/zones/dns-sec-status", nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create GetZonesDNSSecStatus request: %w", err)
	}

	var result GetZonesDNSSecStatusResponse
	resp, err := d.Exec(req, &result, params)
	if err != nil {
		return nil, fmt.Errorf("GetZonesDNSSecStatus request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, d.Error(resp)
	}

	return &result, nil
}
