package communities

import (
	"github.com/status-im/status-go/protocol/protobuf"
)

type KeyDistributor interface {
	Generate(community *Community, keyActions *EncryptionKeyActions) error
	Distribute(community *Community, keyActions *EncryptionKeyActions) error
}

type EncryptionKeyActionType int

const (
	EncryptionKeyNone EncryptionKeyActionType = iota
	EncryptionKeyAdd
	EncryptionKeyRemove
	EncryptionKeyRekey
	EncryptionKeySendToMembers
)

type EncryptionKeyAction struct {
	ActionType     EncryptionKeyActionType
	Members        map[string]*protobuf.CommunityMember
	RemovedMembers map[string]*protobuf.CommunityMember
}

type EncryptionKeyActions struct {
	// community-level encryption key action
	CommunityKeyAction EncryptionKeyAction

	// channel-level encryption key actions
	ChannelKeysActions map[string]EncryptionKeyAction // key is: chatID
}

func EvaluateCommunityEncryptionKeyActions(origin, modified *Community) *EncryptionKeyActions {
	if origin == nil {
		// `modified` is a new community, create empty `origin` community
		origin = &Community{
			config: &Config{
				ID: modified.config.ID,
				CommunityDescription: &protobuf.CommunityDescription{
					Members:                 map[string]*protobuf.CommunityMember{},
					Permissions:             &protobuf.CommunityPermissions{},
					Identity:                &protobuf.ChatIdentity{},
					Chats:                   map[string]*protobuf.CommunityChat{},
					Categories:              map[string]*protobuf.CommunityCategory{},
					AdminSettings:           &protobuf.CommunityAdminSettings{},
					TokenPermissions:        map[string]*protobuf.CommunityTokenPermission{},
					CommunityTokensMetadata: []*protobuf.CommunityTokenMetadata{},
				},
			},
		}
	}

	changes := EvaluateCommunityChanges(origin, modified)

	result := &EncryptionKeyActions{
		CommunityKeyAction: *evaluateCommunityLevelEncryptionKeyAction(origin, modified, changes),
		ChannelKeysActions: *evaluateChannelLevelEncryptionKeyActions(origin, modified, changes),
	}
	return result
}

func evaluateCommunityLevelEncryptionKeyAction(origin, modified *Community, changes *CommunityChanges) *EncryptionKeyAction {
	return evaluateEncryptionKeyAction(
		origin.Encrypted(),
		modified.Encrypted(),
		changes.ControlNodeChanged != nil,
		modified.config.CommunityDescription.Members,
		changes.MembersAdded,
		changes.MembersRemoved,
	)
}

func evaluateChannelLevelEncryptionKeyActions(origin, modified *Community, changes *CommunityChanges) *map[string]EncryptionKeyAction {
	result := make(map[string]EncryptionKeyAction)

	for channelID := range modified.config.CommunityDescription.Chats {
		membersAdded := make(map[string]*protobuf.CommunityMember)
		membersRemoved := make(map[string]*protobuf.CommunityMember)

		chatChanges, ok := changes.ChatsModified[channelID]
		if ok {
			membersAdded = chatChanges.MembersAdded
			membersRemoved = chatChanges.MembersRemoved
		}

		result[channelID] = *evaluateEncryptionKeyAction(
			origin.ChannelEncrypted(channelID),
			modified.ChannelEncrypted(channelID),
			changes.ControlNodeChanged != nil,
			modified.config.CommunityDescription.Chats[channelID].Members,
			membersAdded,
			membersRemoved,
		)
	}

	return &result
}

func evaluateEncryptionKeyAction(originEncrypted, modifiedEncrypted, controlNodeChanged bool,
	allMembers, membersAdded, membersRemoved map[string]*protobuf.CommunityMember) *EncryptionKeyAction {
	result := &EncryptionKeyAction{
		ActionType: EncryptionKeyNone,
		Members:    map[string]*protobuf.CommunityMember{},
	}

	copyMap := func(source map[string]*protobuf.CommunityMember) map[string]*protobuf.CommunityMember {
		to := make(map[string]*protobuf.CommunityMember)
		for pubKey, member := range source {
			to[pubKey] = member
		}
		return to
	}

	// control node changed on closed community/channel
	if controlNodeChanged && modifiedEncrypted {
		result.ActionType = EncryptionKeyRekey
		result.Members = copyMap(allMembers)
		return result
	}

	// encryption was just added
	if modifiedEncrypted && !originEncrypted {
		result.ActionType = EncryptionKeyAdd
		result.Members = copyMap(allMembers)
		return result
	}

	// encryption was just removed
	if !modifiedEncrypted && originEncrypted {
		result.ActionType = EncryptionKeyRemove
		result.Members = copyMap(allMembers)
		return result
	}

	// open community/channel does not require any actions
	if !modifiedEncrypted {
		return result
	}

	if len(membersRemoved) > 0 {
		result.ActionType = EncryptionKeyRekey
		result.Members = copyMap(allMembers)
		result.RemovedMembers = copyMap(membersRemoved)
	} else if len(membersAdded) > 0 {
		result.ActionType = EncryptionKeySendToMembers
		result.Members = copyMap(membersAdded)
	}

	return result
}