package mailgun

import (
	"context"
	"errors"
	"strconv"
)

type TemplateEngine string

// Used by CreateTemplate() and AddTemplateVersion() to specify the template engine
const (
	TemplateEngineMustache   = TemplateEngine("mustache")
	TemplateEngineHandlebars = TemplateEngine("handlebars")
	TemplateEngineGo         = TemplateEngine("go")
)

type Template struct {
	Name        string          `json:"name"`
	Description string          `json:"description"`
	CreatedAt   RFC2822Time     `json:"createdAt"`
	Version     TemplateVersion `json:"version,omitempty"`
}

type templateResp struct {
	Item    Template `json:"template"`
	Message string   `json:"message"`
}

type templateListResp struct {
	Items  []Template `json:"items"`
	Paging Paging     `json:"paging"`
}

// Create a new template which can be used to attach template versions to
func (mg *MailgunImpl) CreateTemplate(ctx context.Context, template *Template) error {
	r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint))
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())

	payload := newUrlEncodedPayload()

	if template.Name != "" {
		payload.addValue("name", template.Name)
	}
	if template.Description != "" {
		payload.addValue("description", template.Description)
	}

	if template.Version.Engine != "" {
		payload.addValue("engine", string(template.Version.Engine))
	}
	if template.Version.Template != "" {
		payload.addValue("template", template.Version.Template)
	}
	if template.Version.Comment != "" {
		payload.addValue("comment", template.Version.Comment)
	}
	if template.Version.Tag != "" {
		payload.addValue("tag", template.Version.Tag)
	}

	var resp templateResp
	if err := postResponseFromJSON(ctx, r, payload, &resp); err != nil {
		return err
	}
	*template = resp.Item
	return nil
}

// GetTemplate gets a template given the template name
func (mg *MailgunImpl) GetTemplate(ctx context.Context, name string) (Template, error) {
	r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + name)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	r.addParameter("active", "yes")

	var resp templateResp
	err := getResponseFromJSON(ctx, r, &resp)
	if err != nil {
		return Template{}, err
	}
	return resp.Item, nil
}

// Update the name and description of a template
func (mg *MailgunImpl) UpdateTemplate(ctx context.Context, template *Template) error {
	if template.Name == "" {
		return errors.New("UpdateTemplate() Template.Name cannot be empty")
	}

	r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + template.Name)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	p := newUrlEncodedPayload()

	if template.Name != "" {
		p.addValue("name", template.Name)
	}
	if template.Description != "" {
		p.addValue("description", template.Description)
	}

	var resp templateResp
	err := putResponseFromJSON(ctx, r, p, &resp)
	if err != nil {
		return err
	}
	*template = resp.Item
	return nil
}

// Delete a template given a template name
func (mg *MailgunImpl) DeleteTemplate(ctx context.Context, name string) error {
	r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + name)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	_, err := makeDeleteRequest(ctx, r)
	return err
}

type TemplatesIterator struct {
	templateListResp
	mg  Mailgun
	err error
}

type ListTemplateOptions struct {
	Limit  int
	Active bool
}

// List all available templates
func (mg *MailgunImpl) ListTemplates(opts *ListTemplateOptions) *TemplatesIterator {
	r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint))
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	if opts != nil {
		if opts.Limit != 0 {
			r.addParameter("limit", strconv.Itoa(opts.Limit))
		}
		if opts.Active {
			r.addParameter("active", "yes")
		}
	}
	url, err := r.generateUrlWithParameters()
	return &TemplatesIterator{
		mg:               mg,
		templateListResp: templateListResp{Paging: Paging{Next: url, First: url}},
		err:              err,
	}
}

// If an error occurred during iteration `Err()` will return non nil
func (ti *TemplatesIterator) Err() error {
	return ti.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 (ti *TemplatesIterator) Next(ctx context.Context, items *[]Template) bool {
	if ti.err != nil {
		return false
	}
	ti.err = ti.fetch(ctx, ti.Paging.Next)
	if ti.err != nil {
		return false
	}
	cpy := make([]Template, len(ti.Items))
	copy(cpy, ti.Items)
	*items = cpy
	if len(ti.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 (ti *TemplatesIterator) First(ctx context.Context, items *[]Template) bool {
	if ti.err != nil {
		return false
	}
	ti.err = ti.fetch(ctx, ti.Paging.First)
	if ti.err != nil {
		return false
	}
	cpy := make([]Template, len(ti.Items))
	copy(cpy, ti.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 (ti *TemplatesIterator) Last(ctx context.Context, items *[]Template) bool {
	if ti.err != nil {
		return false
	}
	ti.err = ti.fetch(ctx, ti.Paging.Last)
	if ti.err != nil {
		return false
	}
	cpy := make([]Template, len(ti.Items))
	copy(cpy, ti.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 (ti *TemplatesIterator) Previous(ctx context.Context, items *[]Template) bool {
	if ti.err != nil {
		return false
	}
	if ti.Paging.Previous == "" {
		return false
	}
	ti.err = ti.fetch(ctx, ti.Paging.Previous)
	if ti.err != nil {
		return false
	}
	cpy := make([]Template, len(ti.Items))
	copy(cpy, ti.Items)
	*items = cpy
	if len(ti.Items) == 0 {
		return false
	}
	return true
}

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

	return getResponseFromJSON(ctx, r, &ti.templateListResp)
}
