package internal

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

const (
	defaultBaseURL = "https://api.dns.constellix.com"
	defaultVersion = "v1"
)

// Client the Constellix client.
type Client struct {
	BaseURL    string
	HTTPClient *http.Client

	common service // Reuse a single struct instead of allocating one for each service on the heap.

	// Services used for communicating with the API
	Domains    *DomainService
	TxtRecords *TxtRecordService
}

// NewClient Creates a Constellix client.
func NewClient(httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}

	client := &Client{
		BaseURL:    defaultBaseURL,
		HTTPClient: httpClient,
	}

	client.common.client = client
	client.Domains = (*DomainService)(&client.common)
	client.TxtRecords = (*TxtRecordService)(&client.common)

	return client
}

type service struct {
	client *Client
}

// do sends an API request and returns the API response.
func (c *Client) do(req *http.Request, v interface{}) error {
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer func() { _ = resp.Body.Close() }()

	err = checkResponse(resp)
	if err != nil {
		return err
	}

	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read body: %w", err)
	}

	if err = json.Unmarshal(raw, v); err != nil {
		return fmt.Errorf("unmarshaling %T error: %w: %s", v, err, string(raw))
	}

	return nil
}

func (c *Client) createEndpoint(fragment ...string) (string, error) {
	baseURL, err := url.Parse(c.BaseURL)
	if err != nil {
		return "", err
	}

	endpoint, err := baseURL.Parse(path.Join(fragment...))
	if err != nil {
		return "", err
	}

	return endpoint.String(), nil
}

func checkResponse(resp *http.Response) error {
	if resp.StatusCode == http.StatusOK {
		return nil
	}

	data, err := io.ReadAll(resp.Body)
	if err == nil && data != nil {
		msg := &APIError{StatusCode: resp.StatusCode}

		if json.Unmarshal(data, msg) != nil {
			return fmt.Errorf("API error: status code: %d: %v", resp.StatusCode, string(data))
		}

		switch resp.StatusCode {
		case http.StatusNotFound:
			return &NotFound{APIError: msg}
		case http.StatusBadRequest:
			return &BadRequest{APIError: msg}
		default:
			return msg
		}
	}

	return fmt.Errorf("API error, status code: %d", resp.StatusCode)
}
