// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================

package subscribe_list

import (
	mail_v1 "billionmail-core/api/mail_boxes/v1"
	"billionmail-core/internal/model/entity"
	"billionmail-core/internal/service/batch_mail"
	"billionmail-core/internal/service/domains"
	"billionmail-core/internal/service/mail_boxes"
	"billionmail-core/internal/service/mail_service"
	"billionmail-core/internal/service/public"
	"context"
	"database/sql"
	"fmt"
	"github.com/gogf/gf/util/grand"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gfile"
	"os"
	"path/filepath"
	"strings"
)

func getGroupByToken(token string) (*entity.ContactGroup, error) {
	var group entity.ContactGroup
	err := g.DB().Model("bm_contact_groups").Where("token", token).Scan(&group)
	if err != nil && err == sql.ErrNoRows {
		err = nil
	}
	return &group, err
}

func getContactByEmailAndGroup(email string, groupId int) (*entity.Contact, error) {
	var contact entity.Contact
	err := g.DB().Model("bm_contacts").
		Where("group_id", groupId).
		Where("email", email).
		Scan(&contact)
	if err != nil && err == sql.ErrNoRows {
		err = nil
	}

	return &contact, err
}

func addOrUpdateContact(email string, groupId int, attribs map[string]string, status int) error {

	// If exists, update; otherwise, insert
	var contact entity.Contact
	err := g.DB().Model("bm_contacts").
		Where("email", email).
		Where("group_id", groupId).
		Scan(&contact)

	if err != nil && err == sql.ErrNoRows {
		err = nil
	}

	if err != nil {
		return err
	}

	if g.IsEmpty(contact) {
		// Insert new contact
		contact = entity.Contact{
			Email:   email,
			GroupId: groupId,
			Status:  status,
			Attribs: attribs,
		}
		_, err = g.DB().Model("bm_contacts").Data(contact).OmitEmpty().Insert()
		if err != nil {
			return err
		}
	}

	// Update existing contact
	contact.Status = status
	contact.Attribs = attribs
	_, err = g.DB().Model("bm_contacts").Data(contact).Where("id", contact.Id).Update()
	if err != nil {
		return err
	}

	return nil
}

// Update contact status: confirm (0,1), subscribe = 1
func updateContactStatus(email string, groupId int, status int) error {
	_, err := g.DB().Model("bm_contacts").
		Data(g.Map{"status": status, "active": 1}).
		Where("email", email).
		Where("group_id", groupId).
		Update()

	return err
}

func readTemplateFiles(baseFilename string) (htmlContent string, txtContent string, err error) {
	htmlPath := filepath.Join(public.AbsPath("../core/template"), baseFilename+".html")
	txtPath := filepath.Join(public.AbsPath("../core/template"), baseFilename+".txt")

	// Read HTML file
	htmlBytes, err := os.ReadFile(htmlPath)
	if err != nil {
		return "", "", fmt.Errorf("failed to read HTML template: %w", err)
	}

	// Read TXT file (allowed to not exist)
	txtBytes, err := os.ReadFile(txtPath)
	if err != nil {
		if !os.IsNotExist(err) {
			g.Log().Debugf(context.Background(),
				"failed to read TXT template: %v, path: %s", err, txtPath)
		}
		txtBytes = []byte("") // Return empty content if file does not exist
	}

	return string(htmlBytes), string(txtBytes), nil
}

func GetDefaultTemplate(emailType int) (html string, txt string) {
	var (
		defaultHtml string
		defaultTxt  string
		basePath    string
	)

	switch emailType {
	case 1: // Welcome email
		basePath = "default_welcome_email/welcome_email"
		defaultHtml = "<p>Welcome to subscribe!</p>"
		defaultTxt = ""
	case 2: // Confirmation email
		basePath = "default_confirm_email/confirm_email"
		defaultHtml = "<p>Please confirm your subscription.</p>"
		defaultTxt = ""
	case 3: // Unsubscribe email
		basePath = "default_unsubscribe_email/unsubscribe_email"
		defaultHtml = "<p>You have been unsubscribed.</p>"
		defaultTxt = ""
	default:
		return "<p>Welcome to subscribe!</p>", ""
	}

	htmlContent, txtContent, err := readTemplateFiles(basePath)
	if err != nil {
		g.Log().Errorf(context.Background(),
			"failed to load template, using default. Error: %v, Path: %s",
			err, basePath)
		return defaultHtml, defaultTxt
	}

	return htmlContent, txtContent
}

// Send email
func SendMail(ctx context.Context, emailHtml, email, subject, confirmUrl string) error {
	// 1. Load template
	if emailHtml == "" {
		return gerror.New(public.LangCtx(ctx, "The email template is empty"))
	}

	// If {{ ConfirmURL . }} variable exists, replace it with confirmation link
	if strings.Contains(emailHtml, "{{ ConfirmURL . }}") {
		if confirmUrl == "" {
			return gerror.New(public.LangCtx(ctx, "The confirmation email requires a confirmation link"))
		}
		emailHtml = strings.ReplaceAll(emailHtml, "{{ ConfirmURL . }}", confirmUrl)
	}

	// 2. Retrieve contact for variable replacement
	var contact entity.Contact
	_ = g.DB().Model("bm_contacts").Where("email", email).Scan(&contact)

	// 3. Handle unsubscribe link
	content := emailHtml

	// 5. Render content and subject
	engine := batch_mail.GetTemplateEngine()

	personalizedContent, err := engine.RenderEmailTemplate(ctx, content, &contact, nil, "")
	if err != nil {
		return gerror.New(public.LangCtx(ctx, "Failed to render email content"))
	}

	personalizedSubject, err := engine.RenderEmailTemplate(ctx, subject, &contact, nil, "")
	if err != nil {
		return gerror.New(public.LangCtx(ctx, "Failed to render email subject"))
	}

	DefaultDomain, err := GetDefaultDomain()
	if err != nil {
		return gerror.New(public.LangCtx(ctx, "Failed to get default sending domain: {}", err))
	}
	address, err := CreateNoreplyEmail(DefaultDomain)
	if err != nil {
		return gerror.New(public.LangCtx(ctx, "Failed to find noreply email: {}", err))
	}

	// 6. Create sender
	sender, err := mail_service.NewEmailSenderWithLocal(address)
	if err != nil {
		return gerror.New(public.LangCtx(ctx, "Failed to create a sender"))
	}
	defer sender.Close()
	messageId := sender.GenerateMessageID()

	// 7. Create message
	message := mail_service.NewMessage(personalizedSubject, personalizedContent)
	message.SetMessageID(messageId)
	message.SetRealName("noreply")

	// 8. Send email
	err = sender.Send(message, []string{email})
	if err != nil {
		return gerror.New(public.LangCtx(ctx, "Failed to send email: {}", err))
	}

	return nil
}

// Generate token
func GenerateConfirmToken(email, groupToken string) string {
	token, err := batch_mail.GenerateSubscribeConfirmJWT(email, groupToken)
	if err != nil {
		g.Log().Errorf(context.Background(), "Failed to generate subscription confirmation token: %v", err)
		return ""
	}
	return token
}

// Extract email and groupToken from token
func getEmailFromToken(token string) (string, string, error) {
	claims, err := batch_mail.ParseSubscribeConfirmJWT(token)
	if err != nil {
		return "", "", gerror.New("Failed to parse subscription confirmation token: " + err.Error())
	}
	return claims.Email, claims.GroupToken, nil
}

// Build confirmation URL
func buildConfirmUrl(token string) string {
	hostUrl := domains.GetBaseURL()
	return fmt.Sprintf("%s/api/subscribe/confirm?token=%s", hostUrl, token)
}

// Query whether the sender email exists, create if not
func CreateNoreplyEmail(domain string) (string, error) {
	noreplyEmail := "noreply@" + domain
	count, err := g.DB().Model("mailbox").Where("username", noreplyEmail).Count()
	if err != nil {
		return "", gerror.New("Failed to query email")
	}
	if count == 0 {
		ctx := context.Background()
		_ = mail_boxes.Add(ctx, &mail_v1.Mailbox{
			Username:  noreplyEmail,
			Password:  grand.S(16),
			FullName:  "noreply",
			IsAdmin:   0,
			Quota:     5242880,
			LocalPart: "noreply",
			Domain:    domain,
			Active:    1,
		})
	}
	return noreplyEmail, nil
}

// Get default sending domain
func GetDefaultDomain() (string, error) {

	configuredDomain, err := g.DB().Model("bm_options").
		Where("name", "default_sender_domain").
		Value("value")

	if err == nil && configuredDomain != nil {
		return configuredDomain.String(), nil
	}

	// 2. Get the oldest created active domain
	oldestDomain, err := g.DB().Model("domain").
		Order("create_time", "asc").
		Where("active", 1).
		Limit(1).
		Value("domain")
	if err != nil || oldestDomain == nil {
		return "", gerror.New("No available active domains, please add one first")
	}

	// 3. Save to config table
	_, err = g.DB().Model("bm_options").
		Data(g.Map{
			"name":  "default_sender_domain",
			"value": oldestDomain.String(),
		}).
		OnConflict("name").
		Save()

	if err != nil {
		return "", gerror.Wrap(err, "Failed to save default sender domain configuration")
	}

	return oldestDomain.String(), nil
}

// Get subscription form code
func GetSubscribeFormCode(groupToken string) string {

	filePath := public.AbsPath("../core/template/subscribe_form_code.html")
	content := gfile.GetContents(filePath)

	if !strings.Contains(content, "{{ SubmitURL . }}") {
		return ""
	}

	// Link to corresponding page
	var submitUrl string
	hostUrl := domains.GetBaseURL()
	submitUrl = hostUrl + "/api/subscribe/submit?token=" + groupToken

	newContent := strings.ReplaceAll(content, "{{ SubmitURL . }}", submitUrl)

	return newContent

}
