package wechat

import (
	"context"
	"fmt"
	"net/http"
	"sync"

	"github.com/pkg/errors"
	"github.com/silenceper/wechat/v2"
	"github.com/silenceper/wechat/v2/cache"
	"github.com/silenceper/wechat/v2/officialaccount/config"
	"github.com/silenceper/wechat/v2/officialaccount/message"
	"github.com/silenceper/wechat/v2/util"
)

type verificationCallbackFunc func(r *http.Request, verified bool)

// Config is the Service configuration.
type Config struct {
	AppID          string
	AppSecret      string
	Token          string
	EncodingAESKey string
	Cache          cache.Cache
}

// wechatMessageManager abstracts go-wechat's message.Manager for writing unit tests
type wechatMessageManager interface {
	Send(msg *message.CustomerMessage) error
}

// Service encapsulates the WeChat client along with internal state for storing users.
type Service struct {
	config         *Config
	messageManager wechatMessageManager
	userIDs        []string
}

// New returns a new instance of a WeChat notification service.
func New(cfg *Config) *Service {
	wc := wechat.NewWechat()
	wcCfg := &config.Config{
		AppID:          cfg.AppID,
		AppSecret:      cfg.AppSecret,
		Token:          cfg.Token,
		EncodingAESKey: cfg.EncodingAESKey,
		Cache:          cfg.Cache,
	}

	oa := wc.GetOfficialAccount(wcCfg)

	return &Service{
		config:         cfg,
		messageManager: oa.GetCustomerMessageManager(),
	}
}

// WaitForOneOffVerification waits for the verification call from the WeChat backend.
//
// Should be running when (re-)applying settings in wechat configuration.
//
// Set devMode to true when using the sandbox.
//
// See https://developers.weixin.qq.com/doc/offiaccount/en/Basic_Information/Access_Overview.html
func (s *Service) WaitForOneOffVerification(serverURL string, devMode bool, callback verificationCallbackFunc) error {
	srv := &http.Server{Addr: serverURL}
	verificationDone := &sync.WaitGroup{}
	verificationDone.Add(1)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		query := r.URL.Query()

		echoStr := query.Get("echostr")
		if devMode {
			if callback != nil {
				callback(r, true)
			}
			_, _ = w.Write([]byte(echoStr))
			// verification done; dev mode
			verificationDone.Done()
			return
		}

		// perform signature check
		timestamp := query.Get("timestamp")
		nonce := query.Get("nonce")
		suppliedSignature := query.Get("signature")
		computedSignature := util.Signature(s.config.Token, timestamp, nonce)
		if suppliedSignature == computedSignature {
			if callback != nil {
				callback(r, true)
			}
			_, _ = w.Write([]byte(echoStr))
			// verification done; prod mode
			verificationDone.Done()
			return
		}

		// verification not done (keep waiting)
		if callback != nil {
			callback(r, false)
		}
	})

	var err error
	go func() {
		if innerErr := srv.ListenAndServe(); innerErr != http.ErrServerClosed {
			err = errors.Wrapf(innerErr, "failed to start verification listener '%s'", serverURL)
		}
	}()

	// wait until verification is done and shutdown the server
	verificationDone.Wait()

	if innerErr := srv.Shutdown(context.TODO()); innerErr != nil {
		err = errors.Wrap(innerErr, "failed to shutdown verification listerer")
	}

	return err
}

// AddReceivers takes user ids and adds them to the internal users list. The Send method will send
// a given message to all those users.
func (s *Service) AddReceivers(userIDs ...string) {
	s.userIDs = append(s.userIDs, userIDs...)
}

// Send takes a message subject and a message content and sends them to all previously set users.
func (s *Service) Send(ctx context.Context, subject, content string) error {
	for _, userID := range s.userIDs {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			text := fmt.Sprintf("%s\n%s", subject, content)
			err := s.messageManager.Send(message.NewCustomerTextMessage(userID, text))
			if err != nil {
				return errors.Wrapf(err, "failed to send message to WeChat user '%s'", userID)
			}
		}
	}

	return nil
}