// The file implements additional functionality for Connection entity, other than autogenerated code.
// This is not autogenerated.
package connection

import (
	"crypto/md5"
	"encoding/json"
	"sync"

	"github.com/gofrs/uuid"
	"github.com/meshery/meshkit/database"
	"github.com/meshery/schemas/models/core"
	"gorm.io/gorm"
)

var connectionCreation sync.Mutex //Each entity will perform a check and if the connection already doesn't exist, it will create a connection. This lock will make sure that there are no race conditions.

func (h *Connection) GenerateID() (uuid.UUID, error) {
	// Normalize a copy before hashing so that a semantically-empty identity
	// field (nil OR the zero UUID) collapses to a single canonical form. A
	// model's registrant blob may omit credential_id/user_id (marshals to
	// null) or carry a present-but-zero UUID (marshals to the all-zero
	// string); both mean "no credential/user" yet previously produced
	// different md5s, and therefore duplicate content-addressed connection
	// rows for the same registrant. Normalization must not mutate the
	// caller's Connection or the persisted row, so we operate on a copy and
	// only the identity computation observes the canonical form.
	normalized := *h
	normalized.CredentialID = canonicalizeUUIDPtr(normalized.CredentialID)
	normalized.UserID = canonicalizeUUIDPtr(normalized.UserID)

	byt, err := json.Marshal(&normalized)
	if err != nil {
		return uuid.UUID{}, err
	}

	hash := md5.Sum(byt)
	return uuid.UUID(hash), nil
}

// canonicalizeUUIDPtr reduces a semantically-empty UUID pointer to nil so that
// an absent field and a present-but-zero UUID hash identically. A non-zero
// UUID is returned unchanged so it still participates in identity.
func canonicalizeUUIDPtr(id *core.Uuid) *core.Uuid {
	if id == nil || *id == uuid.Nil {
		return nil
	}
	return id
}

func (h *Connection) Create(db *database.Handler) (uuid.UUID, error) {

	hID, err := h.GenerateID()
	if err != nil {
		return uuid.UUID{}, err
	}
	var connection Connection
	connectionCreation.Lock()
	defer connectionCreation.Unlock()
	err = db.First(&connection, "id = ?", hID).Error // check if the connection already exists
	if err != nil && err != gorm.ErrRecordNotFound {
		return uuid.UUID{}, err
	}

	// if not exists then create a new host and return the id
	if err == gorm.ErrRecordNotFound {
		h.ID = hID
		err = db.Create(h).Error
		if err != nil {
			return uuid.UUID{}, err
		}
		return h.ID, nil
	}

	// else return the id of the existing connection
	return connection.ID, nil
}

type MeshsyncDeploymentMode string

const MeshsyncDeploymentModeMetadataKey = "meshsync_deployment_mode"

const (
	MeshsyncDeploymentModeOperator  MeshsyncDeploymentMode = "operator"
	MeshsyncDeploymentModeEmbedded  MeshsyncDeploymentMode = "embedded"
	MeshsyncDeploymentModeUndefined MeshsyncDeploymentMode = "undefined"
	MeshsyncDeploymentModeDefault                          = MeshsyncDeploymentModeEmbedded
)

func MeshsyncDeploymentModeFromString(value string) MeshsyncDeploymentMode {
	switch value {
	case string(MeshsyncDeploymentModeOperator):
		return MeshsyncDeploymentModeOperator
	case string(MeshsyncDeploymentModeEmbedded):
		return MeshsyncDeploymentModeEmbedded
	default:
		return MeshsyncDeploymentModeUndefined
	}
}

func MeshsyncDeploymentModeFromMetadata(metadata core.Map) MeshsyncDeploymentMode {
	raw, exists := metadata[MeshsyncDeploymentModeMetadataKey]
	if !exists {
		return MeshsyncDeploymentModeUndefined
	}

	switch v := raw.(type) {
	case MeshsyncDeploymentMode:
		return v
	case string:
		return MeshsyncDeploymentModeFromString(v)
	default:
		return MeshsyncDeploymentModeUndefined
	}
}

func SetMeshsyncDeploymentModeToMetadata(metadata core.Map, value MeshsyncDeploymentMode) {
	metadata[MeshsyncDeploymentModeMetadataKey] = value
}
