package mailgun

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"

	"github.com/mailgun/mailgun-go/v4/events"
)

type UrlOrUrls struct {
	Urls []string `json:"urls"`
	Url  string   `json:"url"`
}

type WebHooksListResponse struct {
	Webhooks map[string]UrlOrUrls `json:"webhooks"`
}

type WebHookResponse struct {
	Webhook UrlOrUrls `json:"webhook"`
}

// ListWebhooks returns the complete set of webhooks configured for your domain.
// Note that a zero-length mapping is not an error.
func (mg *MailgunImpl) ListWebhooks(ctx context.Context) (map[string][]string, error) {
	r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())

	var body WebHooksListResponse
	err := getResponseFromJSON(ctx, r, &body)
	if err != nil {
		return nil, err
	}

	hooks := make(map[string][]string, 0)
	for k, v := range body.Webhooks {
		if v.Url != "" {
			hooks[k] = []string{v.Url}
		}
		if len(v.Urls) != 0 {
			hooks[k] = append(hooks[k], v.Urls...)
		}
	}
	return hooks, nil
}

// CreateWebhook installs a new webhook for your domain.
func (mg *MailgunImpl) CreateWebhook(ctx context.Context, kind string, urls []string) error {
	r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	p := newUrlEncodedPayload()
	p.addValue("id", kind)
	for _, url := range urls {
		p.addValue("url", url)
	}
	_, err := makePostRequest(ctx, r, p)
	return err
}

// DeleteWebhook removes the specified webhook from your domain's configuration.
func (mg *MailgunImpl) DeleteWebhook(ctx context.Context, kind string) error {
	r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	_, err := makeDeleteRequest(ctx, r)
	return err
}

// GetWebhook retrieves the currently assigned webhook URL associated with the provided type of webhook.
func (mg *MailgunImpl) GetWebhook(ctx context.Context, kind string) ([]string, error) {
	r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	var body WebHookResponse
	if err := getResponseFromJSON(ctx, r, &body); err != nil {
		return nil, err
	}

	if body.Webhook.Url != "" {
		return []string{body.Webhook.Url}, nil
	}
	if len(body.Webhook.Urls) != 0 {
		return body.Webhook.Urls, nil
	}
	return nil, fmt.Errorf("webhook '%s' returned no urls", kind)
}

// UpdateWebhook replaces one webhook setting for another.
func (mg *MailgunImpl) UpdateWebhook(ctx context.Context, kind string, urls []string) error {
	r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
	r.setClient(mg.Client())
	r.setBasicAuth(basicAuthUser, mg.APIKey())
	p := newUrlEncodedPayload()
	for _, url := range urls {
		p.addValue("url", url)
	}
	_, err := makePutRequest(ctx, r, p)
	return err
}

// Represents the signature portion of the webhook POST body
type Signature struct {
	TimeStamp string `json:"timestamp"`
	Token     string `json:"token"`
	Signature string `json:"signature"`
}

// Represents the JSON payload provided when a Webhook is called by mailgun
type WebhookPayload struct {
	Signature Signature      `json:"signature"`
	EventData events.RawJSON `json:"event-data"`
}

// Use this method to parse the webhook signature given as JSON in the webhook response
func (mg *MailgunImpl) VerifyWebhookSignature(sig Signature) (verified bool, err error) {
	h := hmac.New(sha256.New, []byte(mg.APIKey()))
	io.WriteString(h, sig.TimeStamp)
	io.WriteString(h, sig.Token)

	calculatedSignature := h.Sum(nil)
	signature, err := hex.DecodeString(sig.Signature)
	if err != nil {
		return false, err
	}
	if len(calculatedSignature) != len(signature) {
		return false, nil
	}

	return subtle.ConstantTimeCompare(signature, calculatedSignature) == 1, nil
}

// Deprecated: Please use the VerifyWebhookSignature() to parse the latest
// version of WebHooks from mailgun
func (mg *MailgunImpl) VerifyWebhookRequest(req *http.Request) (verified bool, err error) {
	h := hmac.New(sha256.New, []byte(mg.APIKey()))
	io.WriteString(h, req.FormValue("timestamp"))
	io.WriteString(h, req.FormValue("token"))

	calculatedSignature := h.Sum(nil)
	signature, err := hex.DecodeString(req.FormValue("signature"))
	if err != nil {
		return false, err
	}
	if len(calculatedSignature) != len(signature) {
		return false, nil
	}

	return subtle.ConstantTimeCompare(signature, calculatedSignature) == 1, nil
}
