package twilio

import (
	"net/url"
	"strconv"
	"strings"
	"time"
)

// ErrInvalidMessage is returned when a bad message is sent to the Twilio
// receive hook; it tries to clarify why the message is invalid.
type ErrInvalidMessage struct {
	missing []string
	cause   string
}

// Error satisfies the error interface.
func (err *ErrInvalidMessage) Error() string {
	errParts := []string{}
	if len(err.missing) > 0 {
		errParts = append(errParts, "missing="+strings.Join(err.missing, ","))
	}

	if err.cause != "" {
		errParts = append(errParts, "cause="+err.cause)
	}

	return strings.Join(errParts, ", ")
}

// isInvalidMessage returns an error given a list of missing fields and an
// underlying cause, returning nil if the message is valid.
func isInvalidMessage(missing []string, cause string) error {
	if len(missing) == 0 && cause == "" {
		return nil
	}

	return &ErrInvalidMessage{
		missing: missing,
		cause:   cause,
	}
}

// Message represents an incoming Twilio message.
type Message struct {
	Source   string
	To       string
	Body     string
	When     time.Time
	MediaURL string
	ASID     string
	MSID     string
	Price    float64
	Mime     string
}

// MessageFromValues returns a Message from a set of form values.
func MessageFromValues(v url.Values) (*Message, error) {
	missing := []string{}
	cause := ""

	m := &Message{
		Source:   v.Get("From"),
		To:       v.Get("To"),
		Body:     v.Get("Body"),
		MediaURL: v.Get("MediaUrl0"),
		ASID:     v.Get("AccountSid"),
		MSID:     v.Get("MessageSid"),
		When:     time.Now().UTC(),
		Price:    0.0075,
	}

	if m.Source == "" {
		missing = append(missing, "From")
	}

	if m.To == "" {
		missing = append(missing, "To")
	}

	numMediaValue := v.Get("NumMedia")
	if numMediaValue == "" {
		numMediaValue = "0"
	}

	nMedia, err := strconv.Atoi(numMediaValue)
	if err != nil {
		return nil, err
	}

	if nMedia == 0 && m.Body == "" {
		cause = "message has no body or media attachment"
	} else if nMedia != 0 {
		if m.MediaURL == "" {
			missing = append(missing, "MediaUrl0")
		}
		m.Price = 0.01
		if m.Mime = v.Get("MediaContentType0"); m.Mime == "" {
			missing = append(missing, "MediaContentType0")
		}
	}

	if sent := v.Get("DateSent"); sent != "" {
		t, err := time.Parse(time.RFC1123Z, sent)
		if err != nil {
			return nil, err
		}

		m.When = t.UTC()
	}

	if err := isInvalidMessage(missing, cause); err != nil {
		return nil, err
	}
	return m, nil
}