package hostingde

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

const defaultBaseURL = "https://secure.hosting.de/api/dns/v1/json"

// RecordsAddRequest represents a DNS record to add
type RecordsAddRequest struct {
	Name    string `json:"name"`
	Type    string `json:"type"`
	Content string `json:"content"`
	TTL     int    `json:"ttl"`
}

// RecordsDeleteRequest represents a DNS record to remove
type RecordsDeleteRequest struct {
	Name    string `json:"name"`
	Type    string `json:"type"`
	Content string `json:"content"`
	ID      string `json:"id"`
}

// ZoneConfigObject represents the ZoneConfig-section of a hosting.de API response.
type ZoneConfigObject struct {
	AccountID      string `json:"accountId"`
	EmailAddress   string `json:"emailAddress"`
	ID             string `json:"id"`
	LastChangeDate string `json:"lastChangeDate"`
	MasterIP       string `json:"masterIp"`
	Name           string `json:"name"`
	NameUnicode    string `json:"nameUnicode"`
	SOAValues      struct {
		Expire      int    `json:"expire"`
		NegativeTTL int    `json:"negativeTtl"`
		Refresh     int    `json:"refresh"`
		Retry       int    `json:"retry"`
		Serial      string `json:"serial"`
		TTL         int    `json:"ttl"`
	} `json:"soaValues"`
	Status                string   `json:"status"`
	TemplateValues        string   `json:"templateValues"`
	Type                  string   `json:"type"`
	ZoneTransferWhitelist []string `json:"zoneTransferWhitelist"`
}

// ZoneUpdateError represents an error in a ZoneUpdateResponse
type ZoneUpdateError struct {
	Code          int      `json:"code"`
	ContextObject string   `json:"contextObject"`
	ContextPath   string   `json:"contextPath"`
	Details       []string `json:"details"`
	Text          string   `json:"text"`
	Value         string   `json:"value"`
}

// ZoneUpdateMetadata represents the metadata in a ZoneUpdateResponse
type ZoneUpdateMetadata struct {
	ClientTransactionID string `json:"clientTransactionId"`
	ServerTransactionID string `json:"serverTransactionId"`
}

// ZoneUpdateResponse represents a response from hosting.de API
type ZoneUpdateResponse struct {
	Errors   []ZoneUpdateError  `json:"errors"`
	Metadata ZoneUpdateMetadata `json:"metadata"`
	Warnings []string           `json:"warnings"`
	Status   string             `json:"status"`
	Response struct {
		Records []struct {
			Content          string `json:"content"`
			Type             string `json:"type"`
			ID               string `json:"id"`
			Name             string `json:"name"`
			LastChangeDate   string `json:"lastChangeDate"`
			Priority         int    `json:"priority"`
			RecordTemplateID string `json:"recordTemplateId"`
			ZoneConfigID     string `json:"zoneConfigId"`
			TTL              int    `json:"ttl"`
		} `json:"records"`
		ZoneConfig ZoneConfigObject `json:"zoneConfig"`
	} `json:"response"`
}

// ZoneConfigSelector represents a "minimal" ZoneConfig object used in hosting.de API requests
type ZoneConfigSelector struct {
	Name string `json:"name"`
}

// ZoneUpdateRequest represents a hosting.de API ZoneUpdate request
type ZoneUpdateRequest struct {
	AuthToken          string `json:"authToken"`
	ZoneConfigSelector `json:"zoneConfig"`
	RecordsToAdd       []RecordsAddRequest    `json:"recordsToAdd"`
	RecordsToDelete    []RecordsDeleteRequest `json:"recordsToDelete"`
}

func (d *DNSProvider) updateZone(updateRequest ZoneUpdateRequest) (*ZoneUpdateResponse, error) {
	body, err := json.Marshal(updateRequest)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(http.MethodPost, defaultBaseURL+"/zoneUpdate", bytes.NewReader(body))
	if err != nil {
		return nil, err
	}

	resp, err := d.config.HTTPClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error querying API: %v", err)
	}

	defer resp.Body.Close()

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, errors.New(toUnreadableBodyMessage(req, content))
	}

	// Everything looks good; but we'll need the ID later to delete the record
	updateResponse := &ZoneUpdateResponse{}
	err = json.Unmarshal(content, updateResponse)
	if err != nil {
		return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(req, content))
	}

	if updateResponse.Status != "success" && updateResponse.Status != "pending" {
		return updateResponse, errors.New(toUnreadableBodyMessage(req, content))
	}

	return updateResponse, nil
}

func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
	return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", req.URL, string(rawBody))
}