package mailgun

import (
	"encoding/json"
	"fmt"
	"strconv"
)

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

// Mailing list members have an attribute that determines if they've subscribed to the mailing list or not.
// This attribute may be used to filter the results returned by GetSubscribers().
// All, Subscribed, and Unsubscribed provides a convenient and readable syntax for specifying the scope of the search.
var (
	All          *bool = nil
	Subscribed   *bool = &yes
	Unsubscribed *bool = &no
)

// yes and no are variables which provide us the ability to take their addresses.
// Subscribed and Unsubscribed are pointers to these booleans.
//
// We use a pointer to boolean as a kind of trinary data type:
// if nil, the relevant data type remains unspecified.
// Otherwise, its value is either true or false.
var (
	yes bool = true
	no  bool = false
)

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

// A Member structure represents a member of the mailing list.
// The Vars field can represent any JSON-encodable data.
type Member struct {
	Address    string                 `json:"address,omitempty"`
	Name       string                 `json:"name,omitempty"`
	Subscribed *bool                  `json:"subscribed,omitempty"`
	Vars       map[string]interface{} `json:"vars,omitempty"`
}

// GetLists returns the specified set of mailing lists administered by your account.
func (mg *MailgunImpl) GetLists(limit, skip int, filter string) (int, []List, error) {
	r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint))
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	p := newUrlEncodedPayload()
	if limit != DefaultLimit {
		p.addValue("limit", strconv.Itoa(limit))
	}
	if skip != DefaultSkip {
		p.addValue("skip", strconv.Itoa(skip))
	}
	if filter != "" {
		p.addValue("address", filter)
	}
	var envelope struct {
		Items      []List `json:"items"`
		TotalCount int    `json:"total_count"`
	}
	response, err := makeRequest(r, "GET", p)
	if err != nil {
		return -1, nil, err
	}
	err = response.parseFromJSON(&envelope)
	return envelope.TotalCount, envelope.Items, err
}

// CreateList 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) CreateList(prototype List) (List, 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", prototype.AccessLevel)
	}
	response, err := makePostRequest(r, p)
	if err != nil {
		return List{}, err
	}
	var l List
	err = response.parseFromJSON(&l)
	return l, err
}

// DeleteList 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) DeleteList(addr string) error {
	r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	_, err := makeDeleteRequest(r)
	return err
}

// GetListByAddress 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) GetListByAddress(addr string) (List, error) {
	r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	response, err := makeGetRequest(r)
	if err != nil {
		return List{}, err
	}

	var envelope struct {
		List `json:"list"`
	}
	err = response.parseFromJSON(&envelope)
	return envelope.List, err
}

// UpdateList 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) UpdateList(addr string, prototype List) (List, 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", prototype.AccessLevel)
	}
	var l List
	response, err := makePutRequest(r, p)
	if err != nil {
		return l, err
	}
	err = response.parseFromJSON(&l)
	return l, err
}

// GetMembers returns the list of members belonging to the indicated mailing list.
// The s parameter can be set to one of three settings to help narrow the returned data set:
// All indicates that you want both Members and unsubscribed members alike, while
// Subscribed and Unsubscribed indicate you want only those eponymous subsets.
func (mg *MailgunImpl) GetMembers(limit, skip int, s *bool, addr string) (int, []Member, error) {
	r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr))
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	p := newUrlEncodedPayload()
	if limit != DefaultLimit {
		p.addValue("limit", strconv.Itoa(limit))
	}
	if skip != DefaultSkip {
		p.addValue("skip", strconv.Itoa(skip))
	}
	if s != nil {
		p.addValue("subscribed", yesNo(*s))
	}
	var envelope struct {
		TotalCount int      `json:"total_count"`
		Items      []Member `json:"items"`
	}
	response, err := makeRequest(r, "GET", p)
	if err != nil {
		return -1, nil, err
	}
	err = response.parseFromJSON(&envelope)
	return envelope.TotalCount, envelope.Items, err
}

// GetMemberByAddress returns a complete Member structure for a member of a mailing list,
// given only their subscription e-mail address.
func (mg *MailgunImpl) GetMemberByAddress(s, l string) (Member, error) {
	r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, l) + "/" + s)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	response, err := makeGetRequest(r)
	if err != nil {
		return Member{}, err
	}
	var envelope struct {
		Member Member `json:"member"`
	}
	err = response.parseFromJSON(&envelope)
	return envelope.Member, err
}

// CreateMember registers a new member of the indicated mailing list.
// If merge is set to true, then the registration may update an existing Member's settings.
// Otherwise, an error will occur if you attempt to add a member with a duplicate e-mail address.
func (mg *MailgunImpl) CreateMember(merge bool, addr string, prototype Member) error {
	vs, err := json.Marshal(prototype.Vars)
	if err != nil {
		return err
	}

	r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr))
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	p := newFormDataPayload()
	p.addValue("upsert", yesNo(merge))
	p.addValue("address", prototype.Address)
	p.addValue("name", prototype.Name)
	p.addValue("vars", string(vs))
	if prototype.Subscribed != nil {
		p.addValue("subscribed", yesNo(*prototype.Subscribed))
	}
	_, err = makePostRequest(r, p)
	return err
}

// UpdateMember lets you change certain details about the indicated mailing list member.
// Address, Name, Vars, and Subscribed fields may be changed.
func (mg *MailgunImpl) UpdateMember(s, l string, prototype Member) (Member, error) {
	r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, l) + "/" + s)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	p := newFormDataPayload()
	if prototype.Address != "" {
		p.addValue("address", prototype.Address)
	}
	if prototype.Name != "" {
		p.addValue("name", prototype.Name)
	}
	if prototype.Vars != nil {
		vs, err := json.Marshal(prototype.Vars)
		if err != nil {
			return Member{}, err
		}
		p.addValue("vars", string(vs))
	}
	if prototype.Subscribed != nil {
		p.addValue("subscribed", yesNo(*prototype.Subscribed))
	}
	response, err := makePutRequest(r, p)
	if err != nil {
		return Member{}, err
	}
	var envelope struct {
		Member Member `json:"member"`
	}
	err = response.parseFromJSON(&envelope)
	return envelope.Member, err
}

// DeleteMember removes the member from the list.
func (mg *MailgunImpl) DeleteMember(member, addr string) error {
	r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr) + "/" + member)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	_, err := makeDeleteRequest(r)
	return err
}

// CreateMemberList registers multiple Members and non-Member members to a single mailing list
// in a single round-trip.
// u indicates if the existing members should be updated or duplicates should be updated.
// Use All to elect not to provide a default.
// The newMembers list can take one of two JSON-encodable forms: an slice of strings, or
// a slice of Member structures.
// If a simple slice of strings is passed, each string refers to the member's e-mail address.
// Otherwise, each Member needs to have at least the Address field filled out.
// Other fields are optional, but may be set according to your needs.
func (mg *MailgunImpl) CreateMemberList(u *bool, addr string, newMembers []interface{}) error {
	r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr) + ".json")
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.ApiKey())
	p := newFormDataPayload()
	if u != nil {
		p.addValue("upsert", yesNo(*u))
	}
	bs, err := json.Marshal(newMembers)
	if err != nil {
		return err
	}
	fmt.Println(string(bs))
	p.addValue("members", string(bs))
	_, err = makePostRequest(r, p)
	return err
}
