package regfishapi

import (
	"context"
	"net/url"
)

// Nameserver delegation modes returned by GetNameserversByDomain.
const (
	NameserverModeStandard = "standard"
	NameserverModeVanity   = "vanity"
	NameserverModeExternal = "external"
)

// Sort keys accepted by ListDomainsOptions.SortBy.
const (
	DomainSortByDomain            = "domain"
	DomainSortByExpiresAt         = "expires_at"
	DomainSortByCancelled         = "cancelled"
	DomainSortByPrimaryNameserver = "primary_nameserver"
	DomainSortByDNSSECState       = "dnssec_state"
	DomainSortByOwner             = "owner"
	DomainSortByRegisteredAt      = "registered_at"
)

// Filters accepted by ListDomainsOptions.Filters.
const (
	DomainFilterCancelled      = "cancelled"
	DomainFilterDNSSECInactive = "dnssec-inactive"
)

// Sort directions.
const (
	SortAsc  = "asc"
	SortDesc = "desc"
)

// Domain describes a registered domain in the customer's portfolio.
type Domain struct {
	Domain            string `json:"domain"`
	SLD               string `json:"sld"`
	TLD               string `json:"tld"`
	Favorite          bool   `json:"favorite"`
	OwnerContactID    *int   `json:"owner_contact_id"`
	Owner             string `json:"owner"`
	PrimaryNameserver string `json:"primary_nameserver"`
	// DNSSECState is the high-level status: "active", "pending" or "inactive".
	DNSSECState  string `json:"dnssec_state"`
	AutoRenew    bool   `json:"auto_renew"`
	Cancelled    bool   `json:"cancelled"`
	RegisteredAt *int64 `json:"registered_at"`
	ExpiresAt    *int64 `json:"expires_at"`
}

// DomainDetails adds the current delegation to a Domain.
type DomainDetails struct {
	Domain
	// Nameservers holds the currently delegated nameservers, without a
	// trailing dot.
	Nameservers []string `json:"nameservers"`
}

// DomainList is a page of the customer's domain portfolio.
type DomainList struct {
	Domains    []Domain `json:"domains"`
	TotalItems int      `json:"total_items"`
	TotalPages int      `json:"total_pages"`
	Page       int      `json:"page"`
	PageSize   int      `json:"page_size"`
	Search     string   `json:"search"`
	Filters    []string `json:"filters"`
	SortBy     string   `json:"sort_by"`
	SortOrder  string   `json:"sort_order"`
}

// ListDomainsOptions narrows and orders the result of ListDomains. The zero
// value requests the first page with the API's defaults.
type ListDomainsOptions struct {
	// Search matches the domain name, owner label or primary nameserver,
	// case-insensitively and partially.
	Search string
	// Filters holds DomainFilter* values.
	Filters []string
	// SortBy holds a DomainSortBy* value.
	SortBy string
	// SortOrder is SortAsc or SortDesc.
	SortOrder string
	// Page is 1-based; 0 leaves the choice to the API.
	Page int
	// PageSize must be one of 10, 25, 50 or 100; 0 leaves the choice to the API.
	PageSize int
}

func (o *ListDomainsOptions) query() string {
	if o == nil {
		return ""
	}
	q := url.Values{}
	if o.Search != "" {
		q.Set("search", o.Search)
	}
	if len(o.Filters) > 0 {
		q.Set("filter", joinComma(o.Filters))
	}
	if o.SortBy != "" {
		q.Set("sortBy", o.SortBy)
	}
	if o.SortOrder != "" {
		q.Set("sortOrder", o.SortOrder)
	}
	if o.Page > 0 {
		q.Set("page", itoa(o.Page))
	}
	if o.PageSize > 0 {
		q.Set("pageSize", itoa(o.PageSize))
	}
	if len(q) == 0 {
		return ""
	}
	return "?" + q.Encode()
}

// ListDomains lists the customer's registered domains. opts may be nil.
func (c *Client) ListDomains(ctx context.Context, opts *ListDomainsOptions) (DomainList, error) {
	var out DomainList
	err := c.doJSON(ctx, "GET", "/domains"+opts.query(), nil, &out)
	return out, err
}

// GetDomainByName retrieves the details of a single registered domain.
func (c *Client) GetDomainByName(ctx context.Context, domain string) (DomainDetails, error) {
	var out DomainDetails
	err := c.doJSON(ctx, "GET", "/domains/"+pathSegment(domain), nil, &out)
	return out, err
}

// VanitySet is a set of vanity nameservers available on the account.
type VanitySet struct {
	ID          int      `json:"id"`
	Host        string   `json:"host"`
	Nameservers []string `json:"nameservers"`
}

// Nameservers describes the delegation of a domain.
type Nameservers struct {
	Domain string `json:"domain"`
	// Mode is one of NameserverModeStandard, NameserverModeVanity or
	// NameserverModeExternal.
	Mode string `json:"mode"`
	// Current holds the delegated nameservers, without a trailing dot.
	Current []string `json:"current"`
	// VanitySetID is nil unless the domain is delegated to a vanity set.
	VanitySetID *int        `json:"vanity_set_id"`
	VanitySets  []VanitySet `json:"vanity_sets"`
}

// GetNameserversByDomain returns the current delegation of the domain together
// with the vanity nameserver sets available on the account.
func (c *Client) GetNameserversByDomain(ctx context.Context, domain string) (Nameservers, error) {
	var out Nameservers
	err := c.doJSON(ctx, "GET", "/domains/"+pathSegment(domain)+"/nameservers", nil, &out)
	return out, err
}

type nameserversUpdateRequest struct {
	VanitySetID *int `json:"vanity_set_id"`
}

// PutNameserversByDomain delegates the domain to one of the account's vanity
// nameserver sets, or — with a nil vanitySetID — back to the regfish standard
// nameservers. Free-form nameservers are not supported by this endpoint;
// external delegations are managed in the regfish dash.
//
// The change is subject to the Domain-Guardian, the domain's status and a
// daily per-domain change limit, which surface as 403, 409 and 429.
func (c *Client) PutNameserversByDomain(ctx context.Context, domain string, vanitySetID *int) (Nameservers, error) {
	var out Nameservers
	body := nameserversUpdateRequest{VanitySetID: vanitySetID}
	err := c.doJSON(ctx, "PUT", "/domains/"+pathSegment(domain)+"/nameservers", body, &out)
	return out, err
}

// Authinfo is a transfer authorization code generated at the registry.
type Authinfo struct {
	Domain   string `json:"domain"`
	Authinfo string `json:"authinfo"`
}

// RequestAuthinfoByDomain generates the domain's transfer authorization code
// at the registry.
//
// The code enables an outbound transfer — request it only on the domain
// owner's explicit demand. Requests are guarded by the Domain-Guardian and
// limited to 3 per domain and 10 per account per day; every issued code is
// recorded in the domain guard log.
func (c *Client) RequestAuthinfoByDomain(ctx context.Context, domain string) (Authinfo, error) {
	var out Authinfo
	err := c.doJSON(ctx, "POST", "/domains/"+pathSegment(domain)+"/authinfo", nil, &out)
	return out, err
}
