package alert

import (
	"context"
	"fmt"
	"github.com/pingcap/tep/pkg/apis/apiserver"
	"github.com/pingcap/tep/pkg/rbac"
	store "github.com/pingcap/tep/pkg/registry/apiserver"
	"github.com/pingcap/tep/pkg/utils"
	"github.com/pingcap/tep/pkg/utils/alert"
	"github.com/wushilin/stream"
	"k8s.io/apimachinery/pkg/api/errors"
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/registry/rest"
	"time"
)

const MetaName = "alertmanager"

type ReceiversStorage struct {
	store         *store.Store
	groupResource schema.GroupResource
}

func NewReceiversStorage(store *store.Store, groupResource schema.GroupResource) *ReceiversStorage {
	return &ReceiversStorage{
		store:         store,
		groupResource: groupResource,
	}
}

func (s *ReceiversStorage) Kind() string {
	return "AlertReceiver"
}

func (s *ReceiversStorage) New() runtime.Object {
	return &apiserver.AlertReceiver{}
}

func (s *ReceiversStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	if name != MetaName {
		return nil, errors.NewBadRequest("Unknown name")
	}

	list, err := s.NewAlertReceivers(ctx)
	if err != nil {
		return nil, err
	}

	receivers := list.(*apiserver.AlertReceiverList)
	if receivers != nil && len(receivers.Items) > 0 {
		return &receivers.Items[0], nil
	}

	return &apiserver.AlertReceiver{
		Receivers:  []string{},
		ObjectMeta: metav1.ObjectMeta{Name: MetaName, UID: MetaName},
	}, nil
}

func (s *ReceiversStorage) NewAlertReceivers(ctx context.Context) (runtime.Object, error) {
	cfg, err := s.store.Controller().GetAlertmanagerConfig(s.store.AlertmanagerSecretName, s.store.AlertmanagerNamespace)
	if err != nil {
		return nil, errors.NewInternalError(err)
	}

	from2sends := make(map[string][]string, 0)
	from2config := make(map[string]string, 0)
	from2SmartHost := make(map[string]string, 0)
	from2Secret := make(map[string]string, 0)
	stream.FromArray(cfg.Receivers).Each(func(receiver *alert.Receiver) {
		stream.FromArray(receiver.EmailConfigs).Each(func(em *alert.EmailConfig) {
			_, ok := from2sends[em.From]
			if !ok {
				from2sends[em.From] = make([]string, 0)
			}
			from2sends[em.From] = append(from2sends[em.From], em.To)
			from2config[em.From] = em.AuthUsername
			from2SmartHost[em.From] = em.Smarthost
			from2Secret[em.From] = em.AuthPassword
		})
	})

	r := &apiserver.AlertReceiverList{}
	for k, v := range from2sends {
		r.Items = append(r.Items, apiserver.AlertReceiver{
			ObjectMeta: metav1.ObjectMeta{Name: MetaName, UID: MetaName},
			Receivers:  v,
			Send:       k,
			UserName:   from2config[k],
			SmartHost:  from2SmartHost[k],
			Password:   getPassword(ctx, from2Secret[k]),
		})
	}

	return r, nil
}

func getPassword(ctx context.Context, password string) string {
	plain, ok := ctx.Value("plain").(bool)
	if ok && plain {
		return password
	}

	return ""
}

func (s *ReceiversStorage) NewList() runtime.Object {
	return &apiserver.AlertReceiverList{}
}

func (s *ReceiversStorage) NamespaceScoped() bool {
	return false
}

func (s *ReceiversStorage) NewExtraListOptions() runtime.Object {
	return &apiserver.ListOptions{}
}

func (s *ReceiversStorage) List(ctx context.Context, options *metainternalversion.ListOptions, extraOptions runtime.Object) (runtime.Object, error) {
	return s.NewAlertReceivers(ctx)
}

func (s *ReceiversStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	operatorName := utils.GetAPIUsername(ctx)
	if !rbac.CanOperate(s.store.DB, operatorName, rbac.PlatformOperation) {
		return nil, errors.NewForbidden(s.groupResource, operatorName, fmt.Errorf("permission denied"))
	}
	return s.NewAlertReceiver(obj)
}

func (s *ReceiversStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	if name != MetaName {
		return nil, false, errors.NewBadRequest("Unknown name")
	}
	operatorName := utils.GetAPIUsername(ctx)
	if !rbac.CanOperate(s.store.DB, operatorName, rbac.PlatformOperation) {
		return nil, false, errors.NewForbidden(s.groupResource, operatorName, fmt.Errorf("permission denied"))
	}

	ctx = context.WithValue(ctx, "plain", true)
	obj, err := s.Get(ctx, name, nil)
	if err != nil {
		return nil, false, err
	}

	newObj, err := objInfo.UpdatedObject(ctx, obj)
	if err != nil {
		return nil, false, err
	}

	receiver := newObj.(*apiserver.AlertReceiver)
	if receiver.Password == "" {
		receiver.Password = obj.(*apiserver.AlertReceiver).Password
	}

	r, err := s.NewAlertReceiver(newObj)
	if err != nil {
		return nil, false, err
	}

	return r, true, nil
}

func (s ReceiversStorage) NewAlertReceiver(obj runtime.Object) (runtime.Object, error) {
	receiver := obj.(*apiserver.AlertReceiver)
	if receiver.Send == "" || len(receiver.Receivers) == 0 {
		return nil, errors.NewBadRequest("invalid email format")
	}

	cfg, err := s.store.Controller().GetAlertmanagerConfig(s.store.AlertmanagerSecretName, s.store.AlertmanagerNamespace)
	if err != nil {
		return nil, errors.NewInternalError(err)
	}

	if cfg.Global.SMTPSmarthost.Host == "" && cfg.Global.SMTPSmarthost.Port == "" {
		cfg.Global.SMTPSmarthost = alert.HostPort{
			Host: "localhost",
			Port: "25",
		}
		cfg.Global.SMTPFrom = "alertmanager@example.org"
		cfg.Global.SMTPAuthUsername = "alertmanagerUserName"
		cfg.Global.SMTPAuthPassword = "password"
	}

	emcfgs := make([]*alert.EmailConfig, len(receiver.Receivers))
	tls := false
	stream.FromArray(receiver.Receivers).Map(func(r string) *alert.EmailConfig {
		return &alert.EmailConfig{
			From:         receiver.Send,
			To:           r,
			AuthUsername: receiver.UserName,
			AuthPassword: receiver.Password,
			Smarthost:    receiver.SmartHost,
			RequireTLS:   &tls,
		}
	}).CollectTo(emcfgs)

	newReceviers := make([]*alert.Receiver, 0)
	stream.FromArray(cfg.Receivers).Filter(func(r *alert.Receiver) bool {
		if len(r.EmailConfigs) == 0 {
			return true
		}

		cfg.Route.Routes = deleteRoute(cfg.Route.Routes, r.Name)
		return false
	}).Each(func(r *alert.Receiver) {
		newReceviers = append(newReceviers, r)
	})

	name := fmt.Sprintf("tep-%d", time.Now().Unix())
	cfg.Receivers = append(newReceviers, &alert.Receiver{
		Name:         name,
		EmailConfigs: emcfgs,
	})

	route := alert.Route{Receiver: name, Continue: true}
	cfg.Route.Routes = append(cfg.Route.Routes, &route)

	if err := s.store.Controller().AlertmanagerReload(cfg, s.store.AlertmanagerSecretName, s.store.AlertmanagerNamespace); err != nil {
		return nil, errors.NewInternalError(err)
	}

	receiver.Name = name
	return receiver, nil
}

func deleteRoute(routes []*alert.Route, deleteName string) []*alert.Route {
	newR := make([]*alert.Route, 0)
	stream.FromArray(routes).Filter(func(r *alert.Route) bool {
		return r.Receiver != deleteName
	}).Each(func(r *alert.Route) {
		newR = append(newR, r)
	})

	return newR
}
