package task

import (
	"context"
	"errors"
	"fmt"
	"slices"
	"strings"
	"time"

	k8sent "github.com/tidbcloud/devops/entity/k8supgrade"
	k8smod "github.com/tidbcloud/devops/models/k8supgrade"
)

type notifyTarget struct {
	TaskID          uint64
	TaskName        string
	K8sName         string
	TenantName      string
	BusinessRegion  string
	SalesOwnerEmail string
	TAMName         string
}

type tenantCacheEntry struct {
	tenantID        uint64
	tenantName      string
	salesOwnerEmail string
	tamName         string
}

func (s *svc) NotifyTask(ctx context.Context, param k8sent.NotifyParam) error {
	if param.TaskID == 0 {
		return fmt.Errorf("task_id is required")
	}
	if !slices.Contains(s.conf.UpgradePlanConfig.AdminApprovers, param.SentBy) {
		return fmt.Errorf("user %s is not an k8s upgrade admin, only admins can notify tasks", param.SentBy)
	}

	pageURL := s.buildTaskPageURL(param.TaskID)
	targets, err := s.loadNotifyTargets(ctx, param.TaskID)
	if err != nil {
		return err
	}
	if len(targets) == 0 {
		return fmt.Errorf("no included clusters in task")
	}

	notice, nerr := s.GetTaskNotice(ctx, param.TaskID, pageURL)
	if nerr != nil {
		notice = defaultNoticeContent
	}
	if param.CustomMessage != "" {
		notice = param.CustomMessage + "\n\n" + notice
	}
	return s.dispatchTaskNotify(ctx, param, pageURL, targets, notice)
}

func (s *svc) buildTaskPageURL(taskID uint64) string {
	// Prefer notify.pageURLBase if configured; fallback to k8sUpgrade.detailLinkBaseURL.
	base := s.conf.Notify.PageURLBase
	if base == "" {
		base = s.conf.DetailLinkBaseURL
	}
	if base == "" {
		return ""
	}
	// If base contains a formatting verb, treat it as fmt template.
	if strings.Contains(base, "%") {
		return fmt.Sprintf(base, taskID)
	}
	// Otherwise append task id.
	if strings.HasSuffix(base, "/") {
		return fmt.Sprintf("%s%d", base, taskID)
	}
	return fmt.Sprintf("%s/%d", base, taskID)
}

func (s *svc) loadNotifyTargets(ctx context.Context, taskID uint64) ([]notifyTarget, error) {
	task, err := s.taskRepo.GetTask(ctx, taskID, false)
	if err != nil {
		return nil, err
	}

	clusters, err := s.taskRepo.ListK8sClusters(ctx, taskID, false)
	if err != nil {
		return nil, err
	}

	tenantCache := map[uint64]*tenantCacheEntry{}
	out := make([]notifyTarget, 0, len(clusters))
	for _, cluster := range clusters {
		target, ok := s.clusterToNotifyTarget(ctx, task, cluster, tenantCache)
		if !ok {
			continue
		}
		out = append(out, target)
	}
	return out, nil
}

func (s *svc) clusterToNotifyTarget(ctx context.Context, task *k8sent.Task, cluster *k8sent.K8sCluster, cache map[uint64]*tenantCacheEntry) (notifyTarget, bool) {
	contact := s.resolveClusterTenantContact(ctx, cluster, cache)
	tenantName := cluster.TenantName
	if contact.tenantName != "" {
		tenantName = contact.tenantName
	}

	return notifyTarget{
		TaskID:          task.ID,
		TaskName:        task.Name,
		K8sName:         cluster.Name,
		TenantName:      tenantName,
		BusinessRegion:  cluster.BusinessRegion,
		SalesOwnerEmail: contact.salesOwnerEmail,
		TAMName:         contact.tamName,
	}, true
}

func (s *svc) dispatchTaskNotify(ctx context.Context, param k8sent.NotifyParam, pageURL string, targets []notifyTarget, notice string) error {
	channels := param.Channels
	if len(channels) == 0 {
		channels = s.conf.Notify.Channels
	}
	if len(channels) == 0 {
		s.Logger(ctx).Info("no notification channels configured, skip notify")
		return fmt.Errorf("no notification channels configured")
	}

	task, err := s.taskRepo.GetTask(ctx, param.TaskID, false)
	if err != nil {
		return fmt.Errorf("failed to get task: %w", err)
	}

	req := taskNotifyRequest{
		TaskID:        param.TaskID,
		TaskName:      task.Name,
		CloudProvider: task.CloudProvider,
		EOSVersion:    task.VersionToBeUpgraded,
		Deadline:      time.Unix(task.Deadline, 0),
		Notice:        notice,
		PageURL:       pageURL,
		Targets:       targets,
	}

	notifiers := newTaskNotifyChannels(s.Logger(ctx), channels, s.conf.Notify, s.emailSvc, s.larkCli)
	var notifyErrs []error
	for _, notifier := range notifiers {
		deliveries, sendErr := notifier.Send(ctx, req)
		if sendErr != nil {
			notifyErrs = append(notifyErrs, fmt.Errorf("%s: %w", notifier.Name(), sendErr))
			s.recordNotifyLog(ctx, param.TaskID, notifier.Name(), "-", pageURL, "failed", sendErr.Error(), param.SentBy)
			s.Logger(ctx).Info("notify skipped or failed", "channel", notifier.Name(), "target", "-", "status", "failed", "error", sendErr.Error())
		}
		for _, delivery := range deliveries {
			status := "sent"
			errMsg := ""
			if delivery.Err != nil {
				status = "failed"
				errMsg = delivery.Err.Error()
				notifyErrs = append(notifyErrs, fmt.Errorf("%s/%s: %w", notifier.Name(), delivery.Target, delivery.Err))
			}
			s.recordNotifyLog(ctx, param.TaskID, notifier.Name(), delivery.Target, pageURL, status, errMsg, param.SentBy)
			s.Logger(ctx).Info("notify sent", "channel", notifier.Name(), "target", delivery.Target, "status", status, "error", errMsg)
		}
	}
	if len(notifyErrs) > 0 {
		return errors.Join(notifyErrs...)
	}
	return nil
}

func (s *svc) recordNotifyLog(ctx context.Context, taskID uint64, channel, target, pageURL, status, errMsg, sentBy string) {
	log := &k8smod.NotifyLog{
		TaskID:   taskID,
		Channel:  channel,
		Target:   target,
		PageURL:  pageURL,
		Status:   status,
		ErrorMsg: errMsg,
		SentBy:   sentBy,
	}
	_ = s.dbp.WriteDB(ctx).Create(log).Error
}
