package message

import (
	"context"
	"fmt"
	"net/mail"

	"github.com/tidbcloud/account/central/utils"

	mailv2 "github.com/tidbcloud/pkgv2/mail/v2"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/tidbcloud/account/internel/dao/entity"
)

var (
	labelRoleMapper = map[string]string{
		entity.MailLabelOrgOwner:         entity.RoleOrgOwner,
		entity.MailLabelOrgMembers:       entity.RoleOrgMember,
		entity.MailLabelOrgBillingAdmins: entity.RoleOrgBillingAdmin,
		entity.MailLabelOrgAuditAdmins:   entity.RoleOrgAuditAdmin,
	}
)

func (m *MessageSvc) loadEmailsByOrgIDAndLabels(ctx context.Context, orgID uint64, labels ...string) ([]string, error) {
	emailsMap := map[string]bool{}
	org, err := m.hub.Org.GetOrgByOrgID(ctx, m.db, orgID)
	if err != nil {
		return nil, err
	}

	if org.Status == entity.TenantStatusCleared || org.Status == entity.TenantStatusDeleted {
		return nil, nil
	}
	org.AccountManager = utils.GetOrgAccountManager(ctx, orgID)

	for _, label := range labels {
		if label == entity.MailLabelOrgCreator {
			emailsMap[org.Email] = true
			continue
		} else if label == entity.MailLabelOrgManager {
			emailsMap[org.AccountManager] = true
			continue
		}
		role, ok := labelRoleMapper[label]
		if !ok {
			tilog.Info(ctx, "invalid label", zap.Any("label", label))
			return nil, errs.NewBadRequest("invalid label", nil)
		}

		users, err := m.hub.Cross.ListUsersByOrgIDAndRole(ctx, m.db, orgID, role, []string{entity.RoleBindingStatusActive})
		if err != nil {
			return nil, err
		}

		for _, user := range users {
			emailsMap[user.Email] = true
		}
	}

	var emails []string
	for i := range emailsMap {
		if i == "" {
			continue // account manager may be empty
		}

		emails = append(emails, i)
	}

	return emails, nil
}

func (m *MessageSvc) loadDefaultTemplateVars(ctx context.Context, orgID uint64, loadCreatorName bool) map[string]string {
	org, err := m.hub.Org.GetOrgByOrgID(ctx, m.db, orgID)
	if err != nil {
		return nil
	}

	vars := map[string]string{
		"orgId":   fmt.Sprintf("%d", org.ID),
		"orgName": org.Name,
	}

	if loadCreatorName {
		user, err := m.hub.User.GetUserByEmail(ctx, m.db, org.Email)
		if err == nil {
			vars["userName"] = user.FirstName
		}
	}

	return vars
}

func mergeTemplateVars(defaultVars, templateVars map[string]string) map[string]string {
	if defaultVars == nil {
		return templateVars
	}

	for k, v := range templateVars {
		defaultVars[k] = v
	}

	return defaultVars
}

func (m *MessageSvc) sendEmails(
	ctx context.Context, tx *gorm.DB, subject string, orgID uint64, scene string, identifier string, emails []string,
	ccEmails []string, templateID string, templateVars map[string]string, loadCreatorName bool,
) (*entity.EmailReminder, error) {
	_, err := m.hub.Message.GetEmailReminder(ctx, tx, orgID, scene, identifier)
	if err != nil && err != gorm.ErrRecordNotFound {
		tilog.Warn(ctx, "MessageSvc sendEmails GetEmailReminder err", zap.Any("orgID", orgID), zap.Any("scene", scene), zap.Any("identifier", identifier), zap.Any("err", err))
		return nil, err
	}

	logger := tilog.WithContextL(ctx).With(
		zap.String("subject", subject), zap.Uint64("orgID", orgID), zap.String("scene", scene), zap.String("identifier", identifier),
		zap.Strings("emails", emails), zap.Strings("ccEmails", ccEmails), zap.String("templateID", templateID),
	)

	if err == nil { // we found an existing email reminder as it does not return gorm.ErrRecordNotFound
		return nil, errs.NewConflict("email duplicated", nil)
	}

	emailReminder, err := m.hub.Message.CreateEmailReminder(ctx, tx, orgID, scene, identifier)
	if err != nil {
		logger.Error("failed to create email reminder", zap.Error(err))
		return nil, errs.NewInternalServerError("failed to create email reminder", err)
	}

	vars := mergeTemplateVars(m.loadDefaultTemplateVars(ctx, orgID, loadCreatorName), templateVars)
	opts := mailv2.NewDefaultSendMailOpts()
	opts.CC = ccEmails
	err = m.mailFacade.SendMailWithTemplateID(ctx, emails, subject, templateID, vars, opts)
	if err != nil {
		logger.Error("failed to send email", zap.Error(err))
		return nil, errs.NewInternalServerError("failed to send email", err)
	}
	logger.Info("send email success")

	return emailReminder, nil
}

func IsValidEmails(ctx context.Context, emails []string) bool {
	for _, i := range emails {
		_, err := mail.ParseAddress(i)
		if err != nil {
			tilog.Info(ctx, "ValidEmail is false", zap.Any("email", i))
			return false
		}
	}

	return true
}

func (m *MessageSvc) SendEmailByLabels(ctx context.Context, subject string, orgID uint64, scene string, identifier string, toLabel string, ccLabels []string, templateID string, templateVars map[string]string) (*entity.EmailReminder, error) {
	var (
		emailReminder *entity.EmailReminder
		err           error
	)

	err = m.db.Transaction(func(tx *gorm.DB) error {
		toEmails, e := m.loadEmailsByOrgIDAndLabels(ctx, orgID, toLabel)
		if e != nil {
			return errs.NewInternalDB("failed to get to_email", e)
		}
		if len(toEmails) == 0 {
			return errs.NewBadRequest("no valid to_emails found", nil)
		}

		ccEmails, e := m.loadEmailsByOrgIDAndLabels(ctx, orgID, ccLabels...)
		if e != nil {
			return errs.NewInternalDB("failed to get cc_email", e)
		}
		tilog.Info(ctx, "query ccEmails ", zap.Any("len", len(ccEmails)))
		if !(IsValidEmails(ctx, toEmails) && IsValidEmails(ctx, ccEmails)) {
			return errs.NewBadRequest("invalid email by label", nil)
		}

		emailReminder, err = m.sendEmails(ctx, tx, subject, orgID, scene, identifier, toEmails, ccEmails, templateID, templateVars, toLabel == entity.MailLabelOrgCreator)
		return err
	})

	return emailReminder, err
}

func (m *MessageSvc) SendEmail(ctx context.Context, subject string, orgID uint64, scene string, identifier string, emails []string, ccEmails []string, templateID string, templateVars map[string]string) (*entity.EmailReminder, error) {
	var (
		emailReminder *entity.EmailReminder
		err           error
	)

	err = m.db.Transaction(func(tx *gorm.DB) error {
		emailReminder, err = m.sendEmails(ctx, tx, subject, orgID, scene, identifier, emails, ccEmails, templateID, templateVars, false)
		return err
	})

	return emailReminder, err
}

func (m *MessageSvc) HoldEmailUniqueID(ctx context.Context, orgID uint64, scene string, identifier string) (*entity.EmailReminder, error) {
	var emailReminder *entity.EmailReminder

	err := m.db.Transaction(func(tx *gorm.DB) error {
		var e error
		emailReminder, e = m.hub.Message.GetEmailReminder(ctx, tx, orgID, scene, identifier)
		if e == nil {
			return nil
		}

		if e != nil && e != gorm.ErrRecordNotFound {
			return e
		}

		emailReminder, e = m.hub.Message.CreateEmailReminder(ctx, tx, orgID, scene, identifier)
		return e
	})

	return emailReminder, err
}

func (m *MessageSvc) ClearEmailUniqueID(ctx context.Context, orgID uint64, scene string, identifier string) error {
	return m.hub.Message.DeleteEmailReminder(ctx, m.db, orgID, scene, identifier)
}

func (m *MessageSvc) QueryEmailUniqueID(ctx context.Context, orgID uint64, scene string, identifier string) (*entity.EmailReminder, error) {
	emailReminder, err := m.hub.Message.GetEmailReminder(ctx, m.db, orgID, scene, identifier)
	if err != nil {
		return nil, errs.WrapGORMError(err, "failed to query email reminder")
	}

	return emailReminder, nil
}
