package regfishapi

import "context"

// Record type constants accepted by the API.
const (
	RecordTypeA     = "A"
	RecordTypeAAAA  = "AAAA"
	RecordTypeCNAME = "CNAME"
	RecordTypeCAA   = "CAA"
	RecordTypeALIAS = "ALIAS"
	RecordTypeTXT   = "TXT"
	RecordTypeMX    = "MX"
)

// Record represents a DNS record with common fields.
//
// Name accepts a fully qualified name ("www.example.com."), a bare subdomain
// ("www") or "@" for the zone apex. Responses return the apex as "@" and all
// other names fully qualified.
//
// Priority is required for MX records; Flags and Tag are required for CAA
// records. The fields below the write set (Auto, Active, TSCreated,
// TSUpdated) are read-only and ignored when a Record is sent to the API.
type Record struct {
	ID         int     `json:"id"`
	Name       string  `json:"name"`
	Type       string  `json:"type"`
	Data       string  `json:"data"`
	TTL        int     `json:"ttl,omitempty"`
	Priority   *int    `json:"priority,omitempty"`
	Annotation *string `json:"annotation,omitempty"`
	Tag        *string `json:"tag,omitempty"`
	Flags      *int    `json:"flags,omitempty"`

	// Auto reports whether the record is managed automatically by regfish.
	// Read-only.
	Auto bool `json:"auto"`
	// Active reports whether the record is currently active. Read-only.
	Active bool `json:"active"`
	// TSCreated is the Unix timestamp of the record's creation. Read-only.
	TSCreated int64 `json:"ts_created"`
	// TSUpdated is the Unix timestamp of the record's last update. Read-only.
	TSUpdated int64 `json:"ts_updated"`
}

// recordPayload is the write projection of Record. Sending the read-only
// fields back to the API would not corrupt anything, but the request schema
// does not define them, so they are stripped here.
type recordPayload struct {
	Name       string  `json:"name"`
	Type       string  `json:"type"`
	Data       string  `json:"data"`
	TTL        int     `json:"ttl,omitempty"`
	Priority   *int    `json:"priority,omitempty"`
	Annotation *string `json:"annotation,omitempty"`
	Tag        *string `json:"tag,omitempty"`
	Flags      *int    `json:"flags,omitempty"`
}

func (r Record) payload() recordPayload {
	return recordPayload{
		Name:       r.Name,
		Type:       r.Type,
		Data:       r.Data,
		TTL:        r.TTL,
		Priority:   r.Priority,
		Annotation: r.Annotation,
		Tag:        r.Tag,
		Flags:      r.Flags,
	}
}

// GetRecordByRRID retrieves details about a specific DNS record by RRID.
func (c *Client) GetRecordByRRID(ctx context.Context, rrid int) (Record, error) {
	var out Record
	err := c.doJSON(ctx, "GET", "/dns/rr/"+itoa(rrid), nil, &out)
	return out, err
}

// AddRecord creates a new DNS record. The zone is detected automatically from
// the record's Name, which must be fully qualified for this endpoint.
//
// With an ordinary customer key, re-creating an existing record fails with
// CodeResourceRecordAlreadyExists. With a capability-scoped ACME credential the
// same call is idempotent: an identical _acme-challenge TXT record that is
// already present is returned as a success, with its existing ID.
//
// That difference is the only recovery path an ACME integration has. Such a
// credential may not call any read operation, so a solver that loses the rrid
// of a challenge record — a process restart between publish and cleanup —
// cannot look it up. Re-posting the identical name and value returns the record
// with its ID so it can then be deleted.
func (c *Client) AddRecord(ctx context.Context, record Record) (Record, error) {
	var out Record
	err := c.doJSON(ctx, "POST", "/dns/rr", record.payload(), &out)
	return out, err
}

// PatchRecord updates a DNS record identified by its name and type — and, for
// every type except A and AAAA, its current data. Use PatchRecordByRRID to
// change a record's name or type, or when several records share the same
// name/type combination: this endpoint requires a unique match.
func (c *Client) PatchRecord(ctx context.Context, record Record) (Record, error) {
	var out Record
	err := c.doJSON(ctx, "PATCH", "/dns/rr", record.payload(), &out)
	return out, err
}

// PatchRecordByRRID updates the DNS record with the given RRID. Unlike
// PatchRecord this can also change the record's name and type.
func (c *Client) PatchRecordByRRID(ctx context.Context, rrid int, record Record) (Record, error) {
	var out Record
	err := c.doJSON(ctx, "PATCH", "/dns/rr/"+itoa(rrid), record.payload(), &out)
	return out, err
}

// DeleteRecordByRRID deletes the DNS record with the given RRID.
func (c *Client) DeleteRecordByRRID(ctx context.Context, rrid int) error {
	return c.doJSON(ctx, "DELETE", "/dns/rr/"+itoa(rrid), nil, nil)
}

// ListRecordsByDomain retrieves all DNS records stored in the zone of the
// given domain. NS records are not part of the result; use GetDNSZoneByDomain
// for a view that includes the zone's delegation.
func (c *Client) ListRecordsByDomain(ctx context.Context, domain string) ([]Record, error) {
	var out []Record
	err := c.doJSON(ctx, "GET", "/dns/"+pathSegment(domain)+"/rr", nil, &out)
	return out, err
}

//
// Backwards compatible wrappers.
//

// GetRecord retrieves details about a specific DNS record by RRID.
//
// Deprecated: use GetRecordByRRID.
func (c *Client) GetRecord(rrid int) (Record, error) {
	return c.GetRecordByRRID(context.Background(), rrid)
}

// CreateRecord creates a new DNS record.
//
// Deprecated: use AddRecord.
func (c *Client) CreateRecord(record Record) (Record, error) {
	return c.AddRecord(context.Background(), record)
}

// UpdateRecord updates a DNS record by the records' name
//
// Deprecated: use PatchRecord.
func (c *Client) UpdateRecord(record Record) (Record, error) {
	return c.PatchRecord(context.Background(), record)
}

// UpdateRecordById updates a DNS record by RRID.
//
// Deprecated: use PatchRecordByRRID.
func (c *Client) UpdateRecordById(rrid int, record Record) (Record, error) {
	return c.PatchRecordByRRID(context.Background(), rrid, record)
}

// DeleteRecord deletes a DNS record by RRID.
//
// Deprecated: use DeleteRecordByRRID.
func (c *Client) DeleteRecord(rrid int) error {
	return c.DeleteRecordByRRID(context.Background(), rrid)
}

// GetRecordsByDomain retrieves all DNS records for a given domain.
//
// Deprecated: use ListRecordsByDomain.
func (c *Client) GetRecordsByDomain(domain string) ([]Record, error) {
	return c.ListRecordsByDomain(context.Background(), domain)
}
