package versio

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

const defaultBaseURL = "https://www.versio.nl/api/v1/"

type dnsRecordsResponse struct {
	Record dnsRecord `json:"domainInfo"`
}

type dnsRecord struct {
	DNSRecords []record `json:"dns_records"`
}

type record struct {
	Type     string `json:"type,omitempty"`
	Name     string `json:"name,omitempty"`
	Value    string `json:"value,omitempty"`
	Priority int    `json:"prio,omitempty"`
	TTL      int    `json:"ttl,omitempty"`
}

type dnsErrorResponse struct {
	Error errorMessage `json:"error"`
}

type errorMessage struct {
	Code    int    `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func (d *DNSProvider) postDNSRecords(domain string, msg interface{}) error {
	reqBody := &bytes.Buffer{}
	err := json.NewEncoder(reqBody).Encode(msg)
	if err != nil {
		return err
	}

	req, err := d.makeRequest(http.MethodPost, "domains/"+domain+"/update", reqBody)
	if err != nil {
		return err
	}

	return d.do(req, nil)
}

func (d *DNSProvider) getDNSRecords(domain string) (*dnsRecordsResponse, error) {
	req, err := d.makeRequest(http.MethodGet, "domains/"+domain+"?show_dns_records=true", nil)
	if err != nil {
		return nil, err
	}

	// we'll need all the dns_records to add the new TXT record
	respData := &dnsRecordsResponse{}
	err = d.do(req, respData)
	if err != nil {
		return nil, err
	}

	return respData, nil
}

func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Request, error) {
	endpoint, err := d.config.BaseURL.Parse(path.Join(d.config.BaseURL.EscapedPath(), uri))
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(method, endpoint.String(), body)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")

	if len(d.config.Username) > 0 && len(d.config.Password) > 0 {
		req.SetBasicAuth(d.config.Username, d.config.Password)
	}

	return req, nil
}

func (d *DNSProvider) do(req *http.Request, result interface{}) error {
	resp, err := d.config.HTTPClient.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return err
	}

	if resp.StatusCode >= http.StatusBadRequest {
		var body []byte
		body, err = io.ReadAll(resp.Body)
		if err != nil {
			return fmt.Errorf("%d: failed to read response body: %w", resp.StatusCode, err)
		}

		respError := &dnsErrorResponse{}
		err = json.Unmarshal(body, respError)
		if err != nil {
			return fmt.Errorf("%d: request failed: %s", resp.StatusCode, string(body))
		}
		return fmt.Errorf("%d: request failed: %s", resp.StatusCode, respError.Error.Message)
	}

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

		if err = json.Unmarshal(content, result); err != nil {
			return fmt.Errorf("%w: %s", err, content)
		}
	}

	return nil
}
