package displayevent

import (
	"context"
	"strconv"

	eventdata "github.com/tidbcloud/eventbus-datastruct/event"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

const (
	BackupCluster       = "BackupCluster"
	DeleteBackup        = "DeleteBackup"
	UpdateBackupSetting = "UpdateBackupSetting"
	RestoreFromBackup   = "RestoreFromBackup"
	CreateExport        = "CreateExport"
	CancelExport        = "CancelExport"
	DeleteExport        = "DeleteExport"
)

type (
	BackupDetail struct {
		BackupID    string `json:"backup_id,omitempty"`
		DisplayName string `json:"display_name,omitempty"`
		BackupType  string `json:"backup_type,omitempty"`
		TriggerType string `json:"trigger_type,omitempty"`
		State       string `json:"state,omitempty"`
	}
	BackupSettingDetail struct {
		ClusterID                  string               `json:"cluster_id,omitempty"`
		PreviousMode               string               `json:"previous_mode,omitempty"`
		NewMode                    string               `json:"new_mode,omitempty"`
		PreviousRetentionDays      int                  `json:"previous_retention_days"`
		NewRetentionDays           int                  `json:"new_retention_days"`
		PreviousDailyBackupTimeUTC string               `json:"previous_daily_backup_time_utc,omitempty"`
		NewDailyBackupTimeUTC      string               `json:"new_daily_backup_time_utc,omitempty"`
		PreviousSchedule           BackupScheduleDetail `json:"previous_schedule"`
		NewSchedule                BackupScheduleDetail `json:"new_schedule"`
	}
	BackupScheduleDetail struct {
		Type                      string `json:"type,omitempty"`
		At                        int    `json:"at"`
		RetentionDays             int    `json:"retention_days"`
		NonZeroClockRetentionDays int    `json:"non_zero_clock_retention_days"`
		ZeroClockRetentionDays    int    `json:"zero_clock_retention_days"`
		DailyBackupMinuteOfDay    int    `json:"daily_backup_minute_of_day"`
	}
	RestoreDetail struct {
		RestoreID       string `json:"restore_id,omitempty"`
		RestoreMode     string `json:"restore_mode,omitempty"`
		BackupID        string `json:"backup_id,omitempty"`
		SourceClusterID string `json:"source_cluster_id,omitempty"`
		TargetClusterID string `json:"target_cluster_id,omitempty"`
		State           string `json:"state,omitempty"`
	}
	ExportDetail struct {
		ExportID    string `json:"export_id,omitempty"`
		DisplayName string `json:"display_name,omitempty"`
		State       string `json:"state,omitempty"`
		FileType    string `json:"file_type,omitempty"`
		Compression string `json:"compression,omitempty"`
	}
)

func New(
	ctx context.Context,
	eventType string,
	status eventdata.EventStatus,
	tenantID uint64,
	clusterID uint64,
	contextID string,
	summary string,
	detail any,
) *clusterevent.DisplayEvent {
	event := &clusterevent.DisplayEvent{
		DisplayEventType: eventType,
		DisplayStatus:    status,
		Severity:         eventdata.EventSeverityInfo,
		Summary:          summary,
		Author:           "",
		AuthorID:         0,
		TenantID:         tenantID,
		ProjectID:        0,
		ClusterID:        clusterID,
		ContextID:        contextID,
		Detail:           detail,
	}

	requestMeta, err := metadata.ExtractRequestMeta(ctx)
	if err != nil {
		tilog.Warn(ctx, "extract request metadata for display event", zap.Error(err))
		return event
	}
	event.Author = requestMeta.Email
	if authorID, err := strconv.ParseUint(requestMeta.UserID, 10, 64); err == nil {
		event.AuthorID = authorID
	} else {
		tilog.Warn(ctx, "parse display event author id", zap.String("user_id", requestMeta.UserID), zap.Error(err))
	}
	return event
}

// Write persists a display event to the WAL on a best-effort basis. Display
// events are a UI side effect and must not block the business state transition
// when the optional WAL write is unavailable.
func Write(ctx context.Context, committer clusterevent.WALCommitter, event *clusterevent.DisplayEvent) {
	if committer == nil || event == nil {
		return
	}
	if err := committer.Write(ctx, clusterevent.TopicDisplayEvent, event); err != nil {
		tilog.Warn(
			ctx,
			"failed to write display event WAL",
			zap.String("event_type", event.DisplayEventType),
			zap.String("display_status", string(event.DisplayStatus)),
			zap.Error(err),
		)
	}
}
