package task

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"text/template"
	"time"

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

const defaultNoticeTitle = "### {{.InfraProvider}} Kubernetes Cluster Upgrade Notice"

const defaultNoticeContent = `
&nbsp;
**Hello everyone, a new round of Kubernetes upgrades has begun. Please pay attention to the relevant upgrade details.**
&nbsp;
#### Background
- **Description:** {{.InfraProvider}} {{.Version}} is approaching the End of Support. We plan to upgrade to version {{.TargetVersion}}.
- **Target Version:** {{.TargetVersion}}
- **Deadline:** {{.Deadline}}
- **Estimated K8s Count:** {{.ClusterCount}}
- **Upgrade Scope:** Control Plane, Addons, Admin Nodes, TiDB Nodes, CDC/DM/O11Y Nodes

&nbsp;
#### Effect
- TiDB rolling restart, if necessary
- CDC/DM/O11Y Restart, the service has minute-level downtime.
- TiDB will be in maintenance mode and cannot perform changes.

&nbsp;
#### Action Required
- Review the cluster list and upgrade plan in the Ops Portal.
- Coordinate with Sales Owner and TAM for customer-facing communication.
- Confirm or adjust maintenance schedules before execution.

&nbsp;
Visit [Kubernetes Upgrade Introduction](https://pingcap.feishu.cn/wiki/R1Iuw0PG3iTJHjklEubclK5Nnvb) for more information about Kubernetes upgrades.
`

type noticeTemplateVars struct {
	Cloud         string
	InfraProvider string
	Version       string
	EOSDate       string
	Deadline      string
	ClusterCount  string
	PageURL       string
	TaskName      string
	TargetVersion string
}

// GetTaskNotice returns the notice for a task.
// It first checks the k8s_upgrade_notice table for a notice that was explicitly saved by an admin.
// If found, the stored content is returned as-is.
// Otherwise, the notice is dynamically generated from the config template and task metadata.
func (s *svc) GetTaskNotice(ctx context.Context, taskID uint64, pageURL string) (string, error) {
	if pageURL == "" {
		pageURL = s.buildTaskPageURL(taskID)
	}

	stored, err := s.getTaskNotice(ctx, taskID)
	if err != nil {
		return "", err
	}
	if stored != nil {
		return formatNotice(stored.Title, stored.Content), nil
	}

	task, err := s.taskRepo.GetTask(ctx, taskID, false)
	if err != nil {
		return "", err
	}
	count, _ := s.countTaskClusters(ctx, taskID)
	baseVersion := k8sent.NewMustVersion(task.VersionToBeUpgraded)
	targetVersion := baseVersion.IncrementMinor(2)
	title, tplContent := s.defaultNoticeTemplate()
	return renderNotice(title, tplContent, noticeTemplateVars{
		InfraProvider: getInfraProvider(task.CloudProvider),
		Version:       task.VersionToBeUpgraded,
		Deadline:      time.Unix(task.Deadline, 0).Format(time.RFC3339),
		ClusterCount:  fmt.Sprintf("%d", count),
		PageURL:       pageURL,
		TaskName:      task.Name,
		TargetVersion: targetVersion.String(),
	})
}

func getInfraProvider(cloudProvider string) string {
	switch cloudProvider {
	case "aws":
		return "EKS"
	case "gcp":
		return "GKE"
	}
	return ""
}

// UpsertTaskNotice persists the final notice text for a task.
// This should only be called when an admin explicitly modifies the notice.
func (s *svc) UpsertTaskNotice(ctx context.Context, taskID uint64, title, content, updatedBy string) error {
	if err := s.updateCheck(ctx, updatedBy); err != nil {
		return err
	}
	return s.dbp.WriteDB(ctx).Model(&k8smod.Notice{}).
		Clauses(clause.OnConflict{UpdateAll: true}).Create(&k8smod.Notice{
		TaskID:    taskID,
		Title:     title,
		Content:   content,
		UpdatedBy: updatedBy,
	}).Error
}

func (s *svc) updateCheck(_ context.Context, updatedBy string) error {
	for _, approver := range s.conf.UpgradePlanConfig.AdminApprovers {
		if approver == updatedBy {
			return nil
		}
	}
	return fmt.Errorf("user %s is not an approver", updatedBy)
}

func (s *svc) defaultNoticeTemplate() (title, content string) {
	cfg := s.conf.Notify.NoticeTemplate
	title = cfg.Title
	content = cfg.Content
	if title == "" {
		title = defaultNoticeTitle
	}
	if content == "" {
		content = defaultNoticeContent
	}
	return title, content
}

func (s *svc) getTaskNotice(ctx context.Context, taskID uint64) (*k8smod.Notice, error) {
	var m k8smod.Notice
	err := s.dbp.ReadDB(ctx).Where("task_id = ?", taskID).First(&m).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &m, nil
}

func (s *svc) countTaskClusters(ctx context.Context, taskID uint64) (int, error) {
	clusters, err := s.taskRepo.ListK8sClusters(ctx, taskID, false)
	if err != nil {
		return 0, err
	}
	return len(clusters), nil
}

func formatNotice(title, content string) string {
	if title == "" {
		title = defaultNoticeTitle
	}
	return fmt.Sprintf("%s\n\n%s", title, content)
}

func renderNotice(title, content string, vars noticeTemplateVars) (string, error) {
	if title == "" {
		title = defaultNoticeTitle
	}
	if content == "" {
		content = defaultNoticeContent
	}

	notice := formatNotice(title, content)

	t, err := template.New("notice").Parse(notice)
	if err != nil {
		return "", fmt.Errorf("parse notice template: %w", err)
	}
	var buf bytes.Buffer
	if err := t.Execute(&buf, vars); err != nil {
		return "", fmt.Errorf("execute notice template: %w", err)
	}
	return buf.String(), nil
}
