package v1

import (
	"context"
	"encoding/json"
	"regexp"
	"time"

	"github.com/pingcap-inc/pchub/internal/apiserver/store"
	"github.com/pingcap-inc/pchub/internal/pkg/code"
	"github.com/pingcap-inc/pchub/internal/pkg/util"
	v1 "github.com/pingcap-inc/pchub/model/v1"
	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/log"
)

type alertService struct {
	store store.Factory
}

type AlertSrv interface {
	ConfirmAlert(c context.Context, ard uint64) error
	CreateAlert(c context.Context, rb *v1.CostAlertCreateBody, r *v1.CloudResource) (*v1.CostAlertListDataItem, error)
	GetAlertRuleByID(c context.Context, id uint64) (*v1.CostAlertRule, error)
	UpdateAlert(c context.Context, rb *v1.CostAlertUpdateBody, alertR *v1.CostAlertRule) (*v1.CostAlertListDataItem, error)
	DeleteAlert(c context.Context, id uint64) error
	ListAlertsByResourceMngID(c context.Context, resourceMID string) (*[]v1.CostAlertListDataItem, error)
}

var _ AlertSrv = (*alertService)(nil)

func newAlert(srv *service) *alertService {
	return &alertService{store: srv.store}
}

func (a *alertService) ConfirmAlert(c context.Context, ard uint64) error {
	log.L(c).Info("AlertSrv.ConfirmAlert service function called")

	arecord, err := a.store.AlertRecord().GetByID(c, ard)
	if err != nil {
		log.L(c).Errorf("AlertSrv.ConfirmAlert service function failed, err: %s", err.Error())

		return err
	}

	d := arecord.CreatedAt.UTC().AddDate(0, 1, 0)
	ex := time.Date(d.Year(), d.Month(), 1, 0, 0, 0, 0, time.UTC)
	dailyAlertExg := regexp.MustCompile(`-Date_\d{4}-\d{2}-\d{2}$`)
	if dailyAlertExg.MatchString(arecord.AlertRuleShadow) {
		ex = arecord.CreatedAt.UTC().AddDate(1, 0, 0)
	}

	if arecord.ExpiredAt.After(ex) || arecord.ExpiredAt.Equal(ex) {
		return nil
	}

	arecord.ExpiredAt = ex

	err = a.store.AlertRecord().Update(c, arecord)
	if err != nil {
		log.L(c).Errorf("AlertSrv.ConfirmAlert service function failed, err: %s", err.Error())

		return err
	}

	return nil
}

func (a *alertService) CreateAlert(c context.Context, rb *v1.CostAlertCreateBody, r *v1.CloudResource) (*v1.CostAlertListDataItem, error) {
	log.L(c).Info("AlertSrv.CreateAlert service function called")

	ar := &v1.CostAlertRule{
		CreatedAt:  time.Now(),
		ResourceID: r.ID,
		Type:       rb.Type,
		Threshold:  rb.Threshold,
	}

	recs, receiversStr, err := a.receiverIDs2AlertReceiver(c, rb.Receivers)
	if err != nil {
		return nil, err
	}
	ar.Receivers = receiversStr

	if rb.StartDate != nil && *rb.StartDate != "" {
		t, err := util.ConvertDateStr2SQLTime(*rb.StartDate)
		if err == nil {
			ar.StartDate = *t
		}
	}
	if rb.EndDate != nil && *rb.EndDate != "" {
		t, err := util.ConvertDateStr2SQLTime(*rb.EndDate)
		if err == nil {
			ar.EndDate = *t
		}
	}
	if rb.Options != nil {
		opts, err := json.Marshal(rb.Options)
		if err == nil {
			ar.Options = string(opts)
		}
	}

	err = a.store.CostAlert().Create(c, ar)
	if err != nil {
		log.L(c).Errorf("AlertSrv.CreateAlert service function failed, err: %s", err.Error())

		return nil, err
	}

	res := &v1.CostAlertListDataItem{
		ID:        ar.ID,
		Type:      ar.Type,
		Threshold: ar.Threshold,
		Receivers: recs,
		Options:   ar.Options,
	}
	if ar.StartDate.Valid {
		res.StartDate = ar.StartDate.Time.Format("2006-01-02")
	}
	if ar.EndDate.Valid {
		res.EndDate = ar.EndDate.Time.Format("2006-01-02")
	}

	return res, nil
}

func (a *alertService) GetAlertRuleByID(c context.Context, id uint64) (*v1.CostAlertRule, error) {
	log.L(c).Info("AlertSrv.GetAlertRuleByID service function called")

	ar, err := a.store.CostAlert().GetByID(c, id)
	if err != nil {
		log.L(c).Errorf("AlertSrv.GetAlertRuleByID service function failed, err: %s", err.Error())

		return nil, err
	}

	return ar, nil
}

func (a *alertService) receiverIDs2AlertReceiver(c context.Context, receivers []v1.CostAlertReceiver) ([]*v1.AlertReceiver, string, error) {
	var recs []*v1.AlertReceiver
	receiversStr := ""

	if receivers != nil && len(receivers) >= 0 {
		rs, err := json.Marshal(receivers)
		if err != nil {
			log.L(c).Errorf("Cannot marshal receivers, err: %s", err.Error())

			return nil, receiversStr, errors.WithCode(code.ErrEncodingJSON, "cannot marshal receivers")
		}
		receiversStr = string(rs)

		userLarkIDs := make([]string, 0)
		chatlarkIDs := make([]string, 0)
		for _, r := range receivers {
			if r.LarkUserID != "" {
				userLarkIDs = append(userLarkIDs, r.LarkUserID)
			}
			if r.LarkChatID != "" {
				chatlarkIDs = append(chatlarkIDs, r.LarkChatID)
			}
		}
		users, err := a.store.User().MGetByLarkIDs(c, userLarkIDs)
		if err != nil {
			log.L(c).Error("Failed to exec UserStore.MGetByLarkIDs: " + err.Error())

			return nil, receiversStr, err
		}
		chats, err := a.store.LarkChat().MGetByLarkIDs(c, chatlarkIDs)
		if err != nil {
			log.L(c).Error("Failed to exec LarkChatStore.MGetByLarkIDs: " + err.Error())

			return nil, receiversStr, err
		}
		for _, u := range users {
			n := u.EnName
			if n == "" {
				n = u.Name
			}
			recs = append(recs, &v1.AlertReceiver{
				Type:   "user",
				Name:   n,
				Avatar: string(u.Avatars),
				LarkID: u.LarkUserID,
			})
		}
		for _, c := range chats {
			recs = append(recs, &v1.AlertReceiver{
				Type:   "chat",
				Name:   c.Name,
				Avatar: c.Avatar,
				LarkID: c.ChatLarkID,
			})
		}
	}

	return recs, receiversStr, nil
}

func (a *alertService) UpdateAlert(c context.Context, rb *v1.CostAlertUpdateBody, alertR *v1.CostAlertRule) (*v1.CostAlertListDataItem, error) {
	log.L(c).Info("AlertSrv.UpdateAlert service function called")

	if rb.Type >= 1 && rb.Type <= 4 {
		alertR.Type = rb.Type
	}

	if rb.Threshold > 0 {
		alertR.Threshold = rb.Threshold
	}

	recs, receiversStr, err := a.receiverIDs2AlertReceiver(c, rb.Receivers)
	if err != nil {
		return nil, err
	}
	alertR.Receivers = receiversStr

	if rb.StartDate != nil && *rb.StartDate != "" {
		t, err := util.ConvertDateStr2SQLTime(*rb.StartDate)
		if err == nil {
			alertR.StartDate = *t
		}
	}
	if rb.EndDate != nil && *rb.EndDate != "" {
		t, err := util.ConvertDateStr2SQLTime(*rb.EndDate)
		if err == nil {
			alertR.EndDate = *t
		}
	}
	if rb.Options != nil {
		opts, err := json.Marshal(rb.Options)
		if err == nil {
			alertR.Options = string(opts)
		}
	}

	err = a.store.CostAlert().Update(c, alertR)
	if err != nil {
		log.L(c).Errorf("CostAlertStore.Update failed, err: %s", err.Error())

		return nil, err
	}

	res := &v1.CostAlertListDataItem{
		ID:        alertR.ID,
		Type:      alertR.Type,
		Threshold: alertR.Threshold,
		Receivers: recs,
		Options:   alertR.Options,
	}
	if alertR.StartDate.Valid {
		res.StartDate = alertR.StartDate.Time.Format("2006-01-02")
	}
	if alertR.EndDate.Valid {
		res.EndDate = alertR.EndDate.Time.Format("2006-01-02")
	}

	return res, nil
}

func (a *alertService) DeleteAlert(c context.Context, id uint64) error {
	log.L(c).Info("AlertSrv.DeleteAlert service function called")

	err := a.store.CostAlert().Delete(c, &v1.CostAlertRule{ID: id})
	if err != nil {
		log.L(c).Errorf("AlertSrv.DeleteAlert service function failed, err: %s", err.Error())

		return err
	}

	return nil
}

func (a *alertService) ListAlertsByResourceMngID(c context.Context, resourceMID string) (*[]v1.CostAlertListDataItem, error) {
	log.L(c).Info("AlertSrv.ListAlertsByResourceMngID service function called")

	alerts, err := a.store.CostAlert().ListByResourceMngID(c, resourceMID)
	if err != nil {
		return nil, err
	}

	res := make([]v1.CostAlertListDataItem, 0, len(*alerts))
	for _, alert := range *alerts {
		item := v1.CostAlertListDataItem{
			ID:        alert.ID,
			Type:      alert.Type,
			Threshold: alert.Threshold,
			Options:   alert.Options,
		}
		if alert.StartDate.Valid {
			item.StartDate = alert.StartDate.Time.Format("2006-01-02")
		}
		if alert.EndDate.Valid {
			item.EndDate = alert.EndDate.Time.Format("2006-01-02")
		}

		if alert.Receivers != "" {
			var rs []v1.CostAlertReceiver
			if err := json.Unmarshal([]byte(alert.Receivers), &rs); err != nil {
				log.L(c).Error("Failed to unmarshal receivers: " + err.Error())

				return nil, err
			}

			recs, _, err := a.receiverIDs2AlertReceiver(c, rs)
			if err != nil {
				return nil, err
			}

			item.Receivers = recs
		}

		res = append(res, item)
	}

	return &res, nil
}
