package task

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/go-logr/logr"
	msgent "github.com/tidbcloud/devops/entity/message"
	"github.com/tidbcloud/devops/pkg/infra/clients/larkcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/larkcli/im"
	"github.com/tidbcloud/devops/pkg/infra/config"
	msgapi "github.com/tidbcloud/devops/pkg/service/message/api"
)

type taskNotifyRequest struct {
	TaskID        uint64
	TaskName      string
	CloudProvider string
	EOSVersion    string
	Deadline      time.Time
	Notice        string
	PageURL       string
	Targets       []notifyTarget
}

type notifyDelivery struct {
	Target string
	Err    error
}

type taskNotifyChannel interface {
	Name() string
	Send(ctx context.Context, req taskNotifyRequest) ([]notifyDelivery, error)
}

func newTaskNotifyChannels(
	logger logr.Logger,
	channelNames []string,
	notifyCfg config.K8sUpgradeNotifyConfig,
	emailSvc msgapi.EmailServiceAPI,
	larkCli *larkcli.LarkClient,
) []taskNotifyChannel {
	out := make([]taskNotifyChannel, 0, len(channelNames))
	for _, name := range channelNames {
		switch name {
		case "lark_group":
			out = append(out, newLarkGroupNotifier(logger, notifyCfg, larkCli))
		case "email":
			out = append(out, newEmailNotifier(logger, notifyCfg, emailSvc))
		}
	}
	return out
}

type emailNotifier struct {
	logger   logr.Logger
	cfg      config.K8sUpgradeNotifyConfig
	emailSvc msgapi.EmailServiceAPI
}

func newEmailNotifier(logger logr.Logger, cfg config.K8sUpgradeNotifyConfig, emailSvc msgapi.EmailServiceAPI) *emailNotifier {
	return &emailNotifier{logger: logger, cfg: cfg, emailSvc: emailSvc}
}

func (n *emailNotifier) Name() string { return "email" }

func (n *emailNotifier) Send(ctx context.Context, req taskNotifyRequest) ([]notifyDelivery, error) {
	emails := collectRecipientEmails(req.Targets, n.cfg.RegionEmailMap)
	if n.cfg.EmailTemplateID == "" || len(emails) == 0 {
		n.logger.Info("no email template configured or no recipients, skip notify")
		return nil, fmt.Errorf("no email template configured or no recipients")
	}
	body := req.Notice + "\n\nLink: " + req.PageURL
	err := n.emailSvc.SendEmail(ctx, msgapi.SendEmailParam{
		Name: "K8s Upgrade Notification",
		Target: msgent.EmailTarget{
			Users: &msgent.EmailTargetUsers{Emails: emails},
		},
		Template: msgent.EmailTemplate{
			ID:        n.cfg.EmailTemplateID,
			Variables: map[string]string{"body": body},
		},
		FromAddress: "no-reply@tidbcloud.com",
		Reason:      "k8s_upgrade_notify",
		Applicant:   "system",
	})
	return []notifyDelivery{{Target: fmt.Sprintf("batch(%d)", len(emails)), Err: err}}, nil
}

type larkGroupNotifier struct {
	logger  logr.Logger
	cfg     config.K8sUpgradeNotifyConfig
	larkCli *larkcli.LarkClient
}

func newLarkGroupNotifier(logger logr.Logger, cfg config.K8sUpgradeNotifyConfig, larkCli *larkcli.LarkClient) *larkGroupNotifier {
	return &larkGroupNotifier{logger: logger, cfg: cfg, larkCli: larkCli}
}

func (n *larkGroupNotifier) Name() string { return "lark_group" }

func (n *larkGroupNotifier) Send(ctx context.Context, req taskNotifyRequest) ([]notifyDelivery, error) {
	if len(req.Targets) == 0 || n.larkCli == nil {
		n.logger.Info("no targets or lark client, skip notify")
		return nil, fmt.Errorf("no targets or lark client")
	}
	if n.cfg.LarkCardTemplateID == "" {
		n.logger.Info("no lark card template id configured, skip notify")
		return nil, fmt.Errorf("no lark card template id configured")
	}
	larkCardTemplateID := n.cfg.LarkCardTemplateID

	deliveries := make([]notifyDelivery, 0)
	if n.cfg.DefaultWebhook != "" {
		deliveries = append(deliveries, notifyDelivery{
			Target: "default",
			Err:    n.sendToWebhook(ctx, req, req.Targets, n.cfg.DefaultWebhook, larkCardTemplateID),
		})
	}
	if len(n.cfg.LarkWebhookMap) > 0 {
		byRegion := groupNotifyTargetsByRegion(req.Targets)
		for region, webhookURL := range n.cfg.LarkWebhookMap {
			region = strings.ToLower(strings.TrimSpace(region))
			regionTargets, ok := byRegion[region]
			if !ok || len(regionTargets) == 0 {
				continue
			}
			if strings.TrimSpace(webhookURL) == "" {
				continue
			}
			deliveries = append(deliveries, notifyDelivery{
				Target: region,
				Err:    n.sendToWebhook(ctx, req, regionTargets, webhookURL, larkCardTemplateID),
			})
		}
	}
	if len(deliveries) == 0 {
		return nil, fmt.Errorf("no lark webhook configured for targets")
	}
	return deliveries, nil
}

func (n *larkGroupNotifier) sendToWebhook(ctx context.Context, req taskNotifyRequest, targets []notifyTarget, webhookURL, larkCardTemplateID string) error {
	if strings.TrimSpace(webhookURL) == "" || strings.TrimSpace(larkCardTemplateID) == "" {
		n.logger.Info("no webhook url or lark card template id, skip notify")
		return nil
	}

	// Build salers as an array of objects: [{id: "<user_id>"}]
	salers := make([]map[string]string, 0)
	if larkIDs := collectRecipientLarkUserIDs(ctx, n.larkCli, targets, n.cfg.RegionEmailMap); len(larkIDs) > 0 {
		for _, id := range larkIDs {
			salers = append(salers, map[string]string{"id": id})
		}
	}

	vars := map[string]any{
		"cloud_provider": req.CloudProvider,
		"eos_version":    req.EOSVersion,
		"cluster_count":  fmt.Sprintf("%d", len(targets)),
		"task_name":      req.TaskName,
		"deadline":       req.Deadline.Format("2006-01-02"),
		"notice":         req.Notice,
		"salers":         salers,
		"task_link": map[string]any{
			"url": req.PageURL,
		},
		"title": req.TaskName,
	}

	msg := im.IMMessageCardTemplateAny{
		MType: "template",
		Data: im.IMMessageCardTemplateDataAny{
			ID:   n.cfg.LarkCardTemplateID,
			Vars: vars,
		},
	}

	return n.larkCli.IM.SendMessageViaWebhook(ctx, webhookURL, msg)
}

func collectRecipientEmails(targets []notifyTarget, regionEmailMap map[string][]string) []string {
	seen := map[string]struct{}{}
	out := make([]string, 0)
	byRegion := groupNotifyTargetsByRegion(targets)
	for region, regionTargets := range byRegion {
		if configured := lookupRegionEmails(regionEmailMap, region); len(configured) > 0 {
			out = appendUniqueEmails(seen, out, configured...)
			continue
		}
		for _, t := range regionTargets {
			out = appendTargetRecipientEmails(seen, out, t)
		}
	}
	return out
}

func appendTargetRecipientEmails(seen map[string]struct{}, out []string, t notifyTarget) []string {
	if t.SalesOwnerEmail != "" {
		out = appendUniqueEmails(seen, out, t.SalesOwnerEmail)
	}
	if t.TAMName != "" && strings.Contains(t.TAMName, "@") {
		out = appendUniqueEmails(seen, out, t.TAMName)
	}
	return out
}

func appendUniqueEmails(seen map[string]struct{}, out []string, emails ...string) []string {
	for _, email := range emails {
		email = strings.TrimSpace(email)
		if email == "" || !strings.Contains(email, "@") {
			continue
		}
		if _, ok := seen[email]; ok {
			continue
		}
		seen[email] = struct{}{}
		out = append(out, email)
	}
	return out
}

func lookupRegionEmails(regionEmailMap map[string][]string, region string) []string {
	if len(regionEmailMap) == 0 {
		return nil
	}
	region = strings.ToLower(strings.TrimSpace(region))
	for k, emails := range regionEmailMap {
		if strings.ToLower(strings.TrimSpace(k)) != region {
			continue
		}
		out := make([]string, 0, len(emails))
		for _, email := range emails {
			if email = strings.TrimSpace(email); email != "" {
				out = append(out, email)
			}
		}
		return out
	}
	return nil
}

func collectRecipientLarkUserIDs(ctx context.Context, larkCli *larkcli.LarkClient, targets []notifyTarget, regionEmailMap map[string][]string) []string {
	if larkCli == nil {
		return nil
	}
	emails := collectRecipientEmails(targets, regionEmailMap)
	if len(emails) == 0 {
		return nil
	}
	contacts, err := larkCli.Contact.BetchGetUserIds(ctx, emails)
	if err != nil {
		return nil
	}
	seen := map[string]struct{}{}
	out := make([]string, 0, len(contacts))
	for _, c := range contacts {
		if c.UserId == nil {
			continue
		}
		if _, ok := seen[*c.UserId]; ok {
			continue
		}
		seen[*c.UserId] = struct{}{}
		out = append(out, *c.UserId)
	}
	return out
}

func groupNotifyTargetsByRegion(targets []notifyTarget) map[string][]notifyTarget {
	byRegion := make(map[string][]notifyTarget)
	for _, t := range targets {
		region := strings.ToLower(strings.TrimSpace(t.BusinessRegion))
		byRegion[region] = append(byRegion[region], t)
	}
	return byRegion
}
