package mailgun

import (
	"context"
	"strconv"
)

// A mailing list may have one of three membership modes.
const (
	// ReadOnly specifies that nobody, including Members, may send messages to
	// the mailing list.  Messages distributed on such lists come from list
	// administrator accounts only.
	AccessLevelReadOnly = "readonly"
	// Members specifies that only those who subscribe to the mailing list may
	// send messages.
	AccessLevelMembers = "members"
	// Everyone specifies that anyone and everyone may both read and submit
	// messages to the mailing list, including non-subscribers.
	AccessLevelEveryone = "everyone"
)

// Specify the access of a mailing list member
type AccessLevel string

// A List structure provides information for a mailing list.
//
// AccessLevel may be one of ReadOnly, Members, or Everyone.
type MailingList struct {
	Address      string      `json:"address,omitempty"`
	Name         string      `json:"name,omitempty"`
	Description  string      `json:"description,omitempty"`
	AccessLevel  AccessLevel `json:"access_level,omitempty"`
	CreatedAt    RFC2822Time `json:"created_at,omitempty"`
	MembersCount int         `json:"members_count,omitempty"`
}

type listsResponse struct {
	Items  []MailingList `json:"items"`
	Paging Paging        `json:"paging"`
}

type mailingListResponse struct {
	MailingList MailingList `json:"member"`
}

type ListsIterator struct {
	listsResponse
	mg  Mailgun
	err error
}

// ListMailingLists returns the specified set of mailing lists administered by your account.
func (mg *MailgunImpl) ListMailingLists(opts *ListOptions) *ListsIterator {
	r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/pages")
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	if opts != nil {
		if opts.Limit != 0 {
			r.addParameter("limit", strconv.Itoa(opts.Limit))
		}
	}
	url, err := r.generateUrlWithParameters()
	return &ListsIterator{
		mg:            mg,
		listsResponse: listsResponse{Paging: Paging{Next: url, First: url}},
		err:           err,
	}
}

// If an error occurred during iteration `Err()` will return non nil
func (li *ListsIterator) Err() error {
	return li.err
}

// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (li *ListsIterator) Next(ctx context.Context, items *[]MailingList) bool {
	if li.err != nil {
		return false
	}
	li.err = li.fetch(ctx, li.Paging.Next)
	if li.err != nil {
		return false
	}
	cpy := make([]MailingList, len(li.Items))
	copy(cpy, li.Items)
	*items = cpy
	if len(li.Items) == 0 {
		return false
	}
	return true
}

// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (li *ListsIterator) First(ctx context.Context, items *[]MailingList) bool {
	if li.err != nil {
		return false
	}
	li.err = li.fetch(ctx, li.Paging.First)
	if li.err != nil {
		return false
	}
	cpy := make([]MailingList, len(li.Items))
	copy(cpy, li.Items)
	*items = cpy
	return true
}

// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (li *ListsIterator) Last(ctx context.Context, items *[]MailingList) bool {
	if li.err != nil {
		return false
	}
	li.err = li.fetch(ctx, li.Paging.Last)
	if li.err != nil {
		return false
	}
	cpy := make([]MailingList, len(li.Items))
	copy(cpy, li.Items)
	*items = cpy
	return true
}

// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (li *ListsIterator) Previous(ctx context.Context, items *[]MailingList) bool {
	if li.err != nil {
		return false
	}
	if li.Paging.Previous == "" {
		return false
	}
	li.err = li.fetch(ctx, li.Paging.Previous)
	if li.err != nil {
		return false
	}
	cpy := make([]MailingList, len(li.Items))
	copy(cpy, li.Items)
	*items = cpy
	if len(li.Items) == 0 {
		return false
	}
	return true
}

func (li *ListsIterator) fetch(ctx context.Context, url string) error {
	r := newHTTPRequest(url)
	r.setClient(li.mg.Client())
	r.setBasicAuth(basicAuthUser, li.mg.APIKey())

	return getResponseFromJSON(ctx, r, &li.listsResponse)
}

// CreateMailingList creates a new mailing list under your Mailgun account.
// You need specify only the Address and Name members of the prototype;
// Description, and AccessLevel are optional.
// If unspecified, Description remains blank,
// while AccessLevel defaults to Everyone.
func (mg *MailgunImpl) CreateMailingList(ctx context.Context, prototype MailingList) (MailingList, error) {
	r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint))
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	p := newUrlEncodedPayload()
	if prototype.Address != "" {
		p.addValue("address", prototype.Address)
	}
	if prototype.Name != "" {
		p.addValue("name", prototype.Name)
	}
	if prototype.Description != "" {
		p.addValue("description", prototype.Description)
	}
	if prototype.AccessLevel != "" {
		p.addValue("access_level", string(prototype.AccessLevel))
	}
	response, err := makePostRequest(ctx, r, p)
	if err != nil {
		return MailingList{}, err
	}
	var l MailingList
	err = response.parseFromJSON(&l)
	return l, err
}

// DeleteMailingList removes all current members of the list, then removes the list itself.
// Attempts to send e-mail to the list will fail subsequent to this call.
func (mg *MailgunImpl) DeleteMailingList(ctx context.Context, addr string) error {
	r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	_, err := makeDeleteRequest(ctx, r)
	return err
}

// GetMailingList allows your application to recover the complete List structure
// representing a mailing list, so long as you have its e-mail address.
func (mg *MailgunImpl) GetMailingList(ctx context.Context, addr string) (MailingList, error) {
	r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	response, err := makeGetRequest(ctx, r)
	if err != nil {
		return MailingList{}, err
	}

	var resp mailingListResponse
	err = response.parseFromJSON(&resp)
	return resp.MailingList, err
}

// UpdateMailingList allows you to change various attributes of a list.
// Address, Name, Description, and AccessLevel are all optional;
// only those fields which are set in the prototype will change.
//
// Be careful!  If changing the address of a mailing list,
// e-mail sent to the old address will not succeed.
// Make sure you account for the change accordingly.
func (mg *MailgunImpl) UpdateMailingList(ctx context.Context, addr string, prototype MailingList) (MailingList, error) {
	r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	p := newUrlEncodedPayload()
	if prototype.Address != "" {
		p.addValue("address", prototype.Address)
	}
	if prototype.Name != "" {
		p.addValue("name", prototype.Name)
	}
	if prototype.Description != "" {
		p.addValue("description", prototype.Description)
	}
	if prototype.AccessLevel != "" {
		p.addValue("access_level", string(prototype.AccessLevel))
	}
	var l MailingList
	response, err := makePutRequest(ctx, r, p)
	if err != nil {
		return l, err
	}
	err = response.parseFromJSON(&l)
	return l, err
}
