package hybrid

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"cwtch.im/cwtch/event"
	"cwtch.im/cwtch/model"
	"cwtch.im/cwtch/model/attr"
	"cwtch.im/cwtch/model/constants"
	"cwtch.im/cwtch/peer"
	"cwtch.im/cwtch/settings"
	"git.openprivacy.ca/openprivacy/connectivity/tor"
	"git.openprivacy.ca/openprivacy/log"
)

type ManagedGroupMemberHandler struct {
	Profile peer.CwtchPeer
}

// HandleChangeGroupMode implements [ManagedGroupHandler].
func (m *ManagedGroupMemberHandler) HandleChangeGroupMode(ctx *ManagedGroupContext, cgm ChangeGroupModeEvent) {
	// nothing to do here?
}

// HandleRemoveMember implements [ManagedGroupHandler].
func (m *ManagedGroupMemberHandler) HandleRemoveMember(ctx *ManagedGroupContext, ame RemoveMemberEvent) {
	// Nothing to do here?
}

// HandleChangeRole implements [ManagedGroupHandler].
func (m *ManagedGroupMemberHandler) HandleChangeRole(ctx *ManagedGroupContext, cpe ChangeMemberRoleEvent) {
	// Nothing to do here?
}

// HandleNewMember implements [ManagedGroupHandler].
func (m *ManagedGroupMemberHandler) HandleNewMember(ctx *ManagedGroupContext, ame AddMemberEvent) {
	// Nothing to do here?
}

func CreateShadowConversation(profile peer.CwtchPeer, onion string) (int, error) {
	if ci, err := profile.NewContactConversation(onion, model.ShadowAccessControl(), false); err == nil {
		err = profile.SetConversationAttribute(ci, attr.LocalScope.ConstructScopedZonedPath(attr.ConversationZone.ConstructZonedPath(constants.Shadowed)), event.True)
		return ci, err
	} else {
		return -1, err
	}
}

// HandleMemberList implements [ManagedGroupHandler].
func (m *ManagedGroupMemberHandler) HandleMemberList(ctx *ManagedGroupContext, ml MemberListEvent) {
	log.Debugf("hgmf memberlistevent: %v", ml.Members)
	data, err := json.Marshal(ml)
	if err != nil {
		return
	}

	// store ml.Members[*].Name in [shadow]profiles, creating if necessary
	for onion, mi := range ml.Members {
		if onion == m.Profile.GetOnion() || len(onion) != 56 {
			continue
		}

		_, err := m.Profile.FetchConversationInfo(onion)
		if err != nil {
			cID, err := CreateShadowConversation(m.Profile, onion)
			if err == nil {
				ci, err := m.Profile.GetConversationInfo(cID)
				if err != nil {
					// something went very wrong...
					return
				}
				name, err := m.Profile.GetConversationAttribute(ci.ID, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)))
				if err != nil || (name != mi.Name && ci.IsShadowed()) {
					m.Profile.SetConversationAttribute(ci.ID, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)), mi.Name)
				}
			}
		}
	}

	szp := attr.ConversationScope.ConstructScopedZonedPath(attr.HybridGroupZone.ConstructZonedPath(constants.GroupMode))
	m.Profile.SetConversationAttributeInt(ctx.GroupConversation.ID, szp, int(ml.GroupMode))

	// update local ACL
	m.HandleACLUpdate(ctx, ml.Members.GetACL())

	m.Profile.PublishEvent(event.NewEvent(event.MemberList, map[event.Field]string{
		event.Data:           string(data),
		event.ConversationID: strconv.Itoa(ctx.GroupConversation.ID),
	}))
}

// HandleACLUpdate implements [ManagedGroupHandler].
func (m *ManagedGroupMemberHandler) HandleACLUpdate(ctx *ManagedGroupContext, acl model.AccessControlList) {
	m.Profile.UpdateConversationAccessControlList(ctx.GroupConversation.ID, acl)
}

// HandleNewMessage implements [ManagedGroupHandler].
func (m *ManagedGroupMemberHandler) HandleNewMessage(ctx *ManagedGroupContext, cme NewClearMessageEvent) {
	log.Infof("New clear message from group %v", cme)
	profile := m.Profile
	if m.Profile.GetOnion() == ctx.OriginalMessage.Author {
		log.Infof("acking New clear message from group %v", cme)
		// ack
		signatureB64 := base64.StdEncoding.EncodeToString(ctx.OriginalMessage.Signature)
		id, err := profile.GetChannelMessageBySignature(ctx.GroupConversation.ID, constants.CHANNEL_CHAT, signatureB64)
		if err == nil {
			profile.UpdateMessageAttribute(ctx.GroupConversation.ID, constants.CHANNEL_CHAT, id, constants.AttrAck, constants.True)
			profile.PublishEvent(event.NewEvent(event.IndexedAcknowledgement, map[event.Field]string{
				event.ConversationID: strconv.Itoa(ctx.GroupConversation.ID),
				event.Index:          strconv.Itoa(id)}))
		}
	} else {
		newmmidstr := strconv.Itoa(int(ctx.OriginalMessage.MemberMessageID))

		szp := attr.LocalScope.ConstructScopedZonedPath(attr.ConversationZone.ConstructZonedPath(fmt.Sprintf("%s::%s", ctx.OriginalMessage.Author, MemberMessageIDKey)))

		// Set the attributes of this message...
		attr := model.Attributes{MemberMessageIDKey: newmmidstr,
			constants.AttrAuthor:        ctx.OriginalMessage.Author,
			constants.AttrAck:           event.True,
			constants.AttrSentTimestamp: time.UnixMilli(int64(ctx.OriginalMessage.Sent)).Format(time.RFC3339Nano)}

		//sza := LocalScope // onattr.ConversationZone, fmt.Sprintf("%s::%s", message.Author, MemberMessageIDKey))
		profile.SetConversationAttributeInt(ctx.GroupConversation.ID, szp, int(ctx.OriginalMessage.MemberMessageID))
		log.Infof("inserting new clear message from group %v", cme)
		// Note: The Channel here is 0...this is the main channel that UIs understand as the default, so this message is
		// becomes part of the conversation...
		mid, err := profile.InternalInsertMessage(ctx.GroupConversation.ID,
			constants.CHANNEL_CHAT,
			ctx.OriginalMessage.Author,
			string(cme.HybridGroupMessage),
			attr,
			ctx.OriginalMessage.Signature)
		contenthash := model.CalculateContentHash(ctx.OriginalMessage.Author, string(cme.HybridGroupMessage))
		if err == nil {
			profile.PublishEvent(event.NewEvent(event.NewMessageFromGroup,
				map[event.Field]string{
					event.ConversationID: strconv.Itoa(ctx.GroupConversation.ID),
					event.TimestampSent:  time.UnixMilli(int64(ctx.OriginalMessage.Sent)).Format(time.RFC3339Nano),
					event.RemotePeer:     ctx.OriginalMessage.Author,
					event.Index:          strconv.Itoa(mid),
					event.Data:           string(cme.HybridGroupMessage),
					event.ContentHash:    contenthash}))
		}
	}
}

type ManagedGroupFunctionality struct {
}

// FunctionalityGate returns GroupModeratorFunctionality functionality - gates now happen on function calls.
func FunctionalityGate() *ManagedGroupFunctionality {
	return new(ManagedGroupFunctionality)
}

func (f ManagedGroupFunctionality) NotifySettingsUpdate(settings settings.GlobalSettings) {
}

func (f ManagedGroupFunctionality) EventsToRegister() []event.Type {
	return []event.Type{event.NewMessageFromPeerEngine}
}

func (f ManagedGroupFunctionality) ExperimentsToRegister() []string {
	return []string{constants.GroupsExperiment}
}

// OnEvent handles File Sharing Hooks like Manifest Received and FileDownloaded
func (f *ManagedGroupFunctionality) OnEvent(ev event.Event, profile peer.CwtchPeer) {
	switch ev.EventType {
	// This is where most of the magic happens for managed groups. A few notes:
	// - CwtchPeer has already taken care of storing this for us, we don't need to worry about that
	// - Group Managers **only** speak overlays and **always** wrap their messages in a ManageGroupEvent anything else is fast-rejected.
	case event.NewMessageFromPeerEngine:
		log.Infof("hgmf member NewMessageFromPeerEngine")
		handle := ev.Data[event.RemotePeer]
		ci, err := profile.FetchConversationInfo(handle)
		if err != nil {
			log.Infof("hgmf member Unknown Group")
			f.NewManagedGroup(profile, handle, false)

			// NOTE: this logic is duplicated from cwtch_peer, as we have to do group manager specific
			// logic to add the group, and we don't have access to those interfaces in cwtch peer
			// so cwtch_peer doesn't do these steps...
			ts, _ := time.Parse(time.RFC3339Nano, ev.Data[event.TimestampReceived])
			id, err := profile.StoreMessage(ev.Data[event.RemotePeer], ev.Data[event.Data], ts)
			if err == nil {
				// Republish as NewMessageFromPeer
				ev.EventType = event.NewMessageFromPeer
				ev.Data[event.Index] = strconv.Itoa(id)
				ev.Data[event.ContentHash] = model.CalculateContentHash(ev.Data[event.RemotePeer], ev.Data[event.Data])
				profile.PublishEvent(ev)
			}

			ci, err = profile.FetchConversationInfo(handle)
			if err != nil {
				break
			}
		}

		// We reject managed group requests for groups not setup as managed groups...
		if ci.IsHybridGroup() {
			log.Infof("hgmf member Unknown Group")
			ctx := NewManagedGroupContext(handle, ci, &ManagedGroupMemberHandler{Profile: profile})
			var cm model.MessageWrapper
			err = json.Unmarshal([]byte(ev.Data[event.Data]), &cm)
			if err != nil {
				log.Errorf("hgmf member Unknown Message %s %v", ev.Data[event.Data], err)
				break
			}
			log.Infof("member processing hybrid group message")
			// The overlay type of this message **must** be ManageGroupEvent
			if cm.Overlay == model.OverlayManageGroupEvent {
				var mge HybridGroupMessage
				err = json.Unmarshal([]byte(cm.Data), &mge)
				if err == nil {
					if err = ctx.Handle(ev.Data[event.Data], &mge); err != nil {
						log.Errorf("issue processing group management event: %v", err)
					}
				} else {
					log.Errorf("issue processing group management event: %v", err)
				}
			}
		}
	}
}

// Define a new managed group, managed by the manager...
func (f *ManagedGroupFunctionality) NewManagedGroup(profile peer.CwtchPeer, manager string, accepted bool) error {

	if !tor.IsValidHostname(manager) {
		return fmt.Errorf("manager handle must be a tor v3 onion")
	}

	// for invite flows we permit the hyrbid group to do stuff in the background
	// but we will no attempt to connect to the group
	// until accepted
	ac := model.NoAccessControl()
	if accepted {
		ac = model.DefaultP2PAccessControl()
	}
	ci, err := profile.NewContactConversation(manager, ac, accepted)
	if err != nil {
		return err
	}

	profile.SetConversationAttribute(ci, attr.LocalScope.ConstructScopedZonedPath(attr.HybridGroupZone.ConstructZonedPath(constants.IsHybridGroup)), constants.True)

	// enable channel 2 on this conversation (hybrid groups management channel)
	key := fmt.Sprintf("channel.%d", 2)
	err = profile.SetConversationAttribute(ci, attr.LocalScope.ConstructScopedZonedPath(attr.ConversationZone.ConstructZonedPath(key)), constants.True)
	if err != nil {
		return fmt.Errorf("could not enable channel 2 on hybrid group: %v", err) // likely a catestrophic error...fail
	}
	err = profile.InitChannel(ci, 2)
	if err != nil {
		return fmt.Errorf("could not enable channel 2 on hybrid group: %v", err) // likely a catestrophic error...fail
	}

	return nil
}

func (f ManagedGroupFunctionality) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, path attr.ScopedZonedPath) {
	// nop hybrid group conversations do not exchange contact requests
}

func (f ManagedGroupFunctionality) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, path attr.ScopedZonedPath, value string, exists bool) {
	// nop hybrid group conversations do not exchange contact requests
}
