package goinwx

import (
	"net/url"

	"github.com/kolo/xmlrpc"
)

// API information.
const (
	APIBaseURL        = "https://api.domrobot.com/xmlrpc/"
	APISandboxBaseURL = "https://api.ote.domrobot.com/xmlrpc/"
	APILanguage       = "en"
)

// Client manages communication with INWX API.
type Client struct {
	// HTTP client used to communicate with the INWX API.
	RPCClient *xmlrpc.Client

	// API username and password
	username string
	password string

	lang string

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

	// Services used for communicating with the API
	Account     *AccountService
	Contacts    *ContactService
	Dnssec      *DNSSecService
	Domains     *DomainService
	Nameservers *NameserverService
}

type service struct {
	client *Client
}

// ClientOptions Options of the API client.
type ClientOptions struct {
	Sandbox bool

	// Language of the return message. (en/de/es)
	Lang string

	// Base URL for API requests (only for client testing purpose).
	BaseURL *url.URL
}

// Request The representation of an API request.
type Request struct {
	ServiceMethod string
	Args          map[string]any
}

// NewClient returns a new INWX API client.
func NewClient(username, password string, opts *ClientOptions) *Client {
	baseURL := getBaseURL(opts).String()

	rpcClient, _ := xmlrpc.NewClient(baseURL, nil)

	client := &Client{
		RPCClient: rpcClient,
		username:  username,
		password:  password,
		lang:      APILanguage,
	}

	if opts != nil && opts.Lang != "" {
		client.lang = opts.Lang
	}

	client.common.client = client
	client.Account = (*AccountService)(&client.common)
	client.Contacts = (*ContactService)(&client.common)
	client.Dnssec = (*DNSSecService)(&client.common)
	client.Domains = (*DomainService)(&client.common)
	client.Nameservers = (*NameserverService)(&client.common)

	return client
}

// NewRequest creates an API request.
func (c *Client) NewRequest(serviceMethod string, args map[string]any) *Request {
	if args != nil {
		args["lang"] = APILanguage
	}

	return &Request{ServiceMethod: serviceMethod, Args: args}
}

// Do sends an API request and returns the API response.
func (c *Client) Do(req *Request) (map[string]any, error) {
	var resp Response

	err := c.RPCClient.Call(req.ServiceMethod, req.Args, &resp)
	if err != nil {
		return nil, err
	}

	return resp.ResponseData, checkResponse(&resp)
}

// checkResponse checks the API response for errors, and returns them if present.
func checkResponse(r *Response) error {
	if c := r.Code; c >= 1000 && c <= 1500 {
		return nil
	}

	return &ErrorResponse{Code: r.Code, Message: r.Message, Reason: r.Reason, ReasonCode: r.ReasonCode}
}

func getBaseURL(opts *ClientOptions) *url.URL {
	var useSandbox bool
	if opts != nil {
		useSandbox = opts.Sandbox
	}

	var baseURL *url.URL

	if useSandbox {
		baseURL, _ = url.Parse(APISandboxBaseURL)
	} else {
		baseURL, _ = url.Parse(APIBaseURL)
	}

	if opts != nil && opts.BaseURL != nil {
		baseURL = opts.BaseURL
	}

	return baseURL
}
