package model

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/tidbcloud/pov-service/internal/pkg/icontext"
)

type OperatorMeta struct {
	Type OperatorType `json:"type"`
	ID   string       `json:"id"`
}

type AuditLog struct {
	ID string `json:"id,omitempty"`

	// Operator Meta, the operator could be service or user...
	OperatorMeta OperatorMeta `json:"operator"`

	// Type should be unique between different object types.
	// Like create_cluster and create_workload
	Type OperateType `json:"type"`

	// ReadableType is a readable string for the operate type
	ReadableType string `json:"readableType"`

	// Object ID is the object being operated, object type could be inferred
	// from type.
	// Object is the object users can feel, like workloadID/ workload test ID
	// So if a user triggers cluster scaling during a workload test, the object id should be the workload test ID
	// maybe add SubObjectID if needed.
	ObjectID string `json:"objectId"`

	OperateAt time.Time `json:"operateAt"`

	// detail is formatted in json currently.
	DetailJSON string `json:"detail"`
}

func (a AuditLog) SetDetailJSON(detailJSON string) AuditLog {
	a.DetailJSON = detailJSON
	return a
}

func (a AuditLog) SetDetailMap(m map[string]string) AuditLog {
	dj, _ := json.Marshal(m)
	a.DetailJSON = string(dj)
	return a
}

func (a AuditLog) SetUpdateDetail(key string, oldValue, newValue string) AuditLog {
	oldKey := fmt.Sprintf("%s_old", key)
	return a.SetDetailMap(map[string]string{
		oldKey: oldValue,
		key:    newValue,
	})
}

func (a AuditLog) AppendMessage(msg string) AuditLog {
	if msg == "" {
		return a
	}
	detailMap := make(map[string]string)
	if a.DetailJSON != "" {
		_ = json.Unmarshal([]byte(a.DetailJSON), &detailMap)
	}

	detailMap["message"] = msg
	return a.SetDetailMap(detailMap)
}

// UpdateTuple represents a single update operation with key, old value, and new value
type UpdateTuple struct {
	Key      string
	OldValue string
	NewValue string
}

// NewUpdateTuple creates a new UpdateTuple for convenience
func NewUpdateTuple(key, oldValue, newValue string) UpdateTuple {
	return UpdateTuple{
		Key:      key,
		OldValue: oldValue,
		NewValue: newValue,
	}
}

// SetUpdateDetails sets multiple update details in tuple style
// Usage: log.SetUpdateDetails(
//
//	UpdateTuple{"minRU", "100", "200"},
//	UpdateTuple{"maxRU", "500", "1000"},
//	UpdateTuple{"replicas", "3", "5"},
//
// )
func (a AuditLog) SetUpdateDetails(updates ...UpdateTuple) AuditLog {
	detailMap := make(map[string]string)

	for _, update := range updates {
		oldKey := fmt.Sprintf("%s_old", update.Key)
		detailMap[oldKey] = update.OldValue
		detailMap[update.Key] = update.NewValue
	}

	return a.SetDetailMap(detailMap)
}

func NewOperationLog(
	ctx context.Context,
	userEmail string,
	operateType OperateType,
	objectID string,
) AuditLog {
	operateAt := icontext.OperateAtFromCtx(ctx)
	return AuditLog{
		OperatorMeta: OperatorMeta{
			Type: OperatorTypeUser,
			ID:   userEmail,
		},
		Type:       operateType,
		ObjectID:   objectID,
		OperateAt:  operateAt,
		DetailJSON: "{}",
	}
}

func NewServiceEventLog(
	serviceID string,
	operateType OperateType,
	objectID string,
) AuditLog {
	return AuditLog{
		OperatorMeta: OperatorMeta{
			Type: OperatorTypeService,
			ID:   serviceID,
		},
		Type:       operateType,
		ObjectID:   objectID,
		OperateAt:  time.Now().UTC(),
		DetailJSON: "{}",
	}
}

func NewPoVEventLog(
	operateType OperateType,
	objectID string,
) AuditLog {
	return NewServiceEventLog("pov-service", operateType, objectID)
}
