// A formal model of the managed group flow
package hybrid

import (
	"encoding/json"
	"fmt"

	"cwtch.im/cwtch/model"
	"git.openprivacy.ca/openprivacy/log"
)

// Interface for capturing stored sideffects of group operations
type ManagedGroupHandler interface {
	HandleNewMember(ctx *ManagedGroupContext, ame AddMemberEvent)
	HandleRemoveMember(ctx *ManagedGroupContext, ame RemoveMemberEvent)
	HandleChangeRole(ctx *ManagedGroupContext, cpe ChangeMemberRoleEvent)
	HandleACLUpdate(ctx *ManagedGroupContext, acl model.AccessControlList)
	HandleNewMessage(ctx *ManagedGroupContext, cme NewClearMessageEvent)
	HandleMemberList(ctx *ManagedGroupContext, memberList MemberListEvent)
	HandleChangeGroupMode(ctx *ManagedGroupContext, cgm ChangeGroupModeEvent)
}

// ManagedGroupContext implements the handling that is relevant to all parties of a managed group (e.g. ACL checks)
// deferring all side effects to a provided ManagedGroupHandler
type ManagedGroupContext struct {
	Handler           ManagedGroupHandler
	GroupID           string
	OriginalPacket    string
	GroupConversation *model.Conversation
	OriginalMessage   *HybridGroupMessage
}

func NewManagedGroupContext(groupID string, gc *model.Conversation, handler ManagedGroupHandler) *ManagedGroupContext {
	return &ManagedGroupContext{GroupConversation: gc, GroupID: groupID, Handler: handler}
}

func (f *ManagedGroupContext) Handle(packet string, mge *HybridGroupMessage) error {

	if !AuthenticateGroupMessage(f.GroupID, f.GroupConversation, *mge) {
		return fmt.Errorf("could not authenticate message")
	}

	f.OriginalPacket = packet
	f.OriginalMessage = mge
	switch mge.EventType {
	case AddMember:
		var ame AddMemberEvent
		err := json.Unmarshal([]byte(mge.MessageBody), &ame)
		if err == nil {
			return f.handleAddMemberEvent(ame)
		}
		return err
	case RemoveMember:
		var rme RemoveMemberEvent
		err := json.Unmarshal([]byte(mge.MessageBody), &rme)
		if err == nil {
			return f.handleRemoveMemberEvent(rme)
		}
		return err
	case ChangeMemberRole:
		var cmr ChangeMemberRoleEvent
		err := json.Unmarshal([]byte(mge.MessageBody), &cmr)
		if err == nil {
			return f.handleChangeMemberRole(cmr)
		}
		return err
	case MemberList:
		var mle MemberListEvent
		err := json.Unmarshal([]byte(mge.MessageBody), &mle)
		if err == nil {
			return f.handleMemberList(mle)
		}
		return err
	case NewClearMessage:
		var cme NewClearMessageEvent
		err := json.Unmarshal([]byte(mge.MessageBody), &cme)
		if err == nil {
			return f.handleClearMessage(cme)
		}
		return err
	case ChangeGroupMode:
		var cgm ChangeGroupModeEvent
		err := json.Unmarshal([]byte(mge.MessageBody), &cgm)
		if err == nil {
			return f.handleChangeGroupMode(cgm)
		}
		return err
	default:
		return fmt.Errorf("invalid managed group event")
	}
}

func (f *ManagedGroupContext) handleChangeMemberRole(cmr ChangeMemberRoleEvent) error {
	acl, err := CheckACL(f.OriginalMessage.Author, f.GroupConversation)
	if err == nil {
		if acl.Promote { // Before doing the more expensive promotion check we can assert that they even have the simple promote authorization
			ac := f.GroupConversation.ACL[cmr.Handle]
			// Check that the promotion is valid given applied mode equality rules
			if acl.AssignedRole.IsAbleToPromoteTo(ac.AssignedRole, cmr.NewRole, f.GroupConversation.GroupMode()) {
				f.Handler.HandleChangeRole(f, cmr)
				ac.AssignedRole = cmr.NewRole
				f.GroupConversation.ACL[cmr.Handle] = ac
				f.Handler.HandleACLUpdate(f, f.GroupConversation.ACL)
				return nil
			}
			return fmt.Errorf("invalid promotion request")
		} else {
			return fmt.Errorf("insufficient promotion authorization")
		}
	}
	return err
}

// handleAddMemberEvent adds a group member to the conversation ACL
func (f *ManagedGroupContext) handleAddMemberEvent(ame AddMemberEvent) error {
	acl, err := CheckACL(f.OriginalMessage.Author, f.GroupConversation)
	if err == nil {
		if acl.Invite {
			f.Handler.HandleNewMember(f, ame)
			acl := f.GroupConversation.ACL
			ac := acl[ame.Handle]
			ac.AssignedRole = model.ROLE_USER
			acl[ame.Handle] = ac
			f.Handler.HandleACLUpdate(f, acl)
			return nil
		}
	}
	return fmt.Errorf("insufficient invite authorization")
}

// handleRemoveMemberEvent adds a group member to the conversation ACL
func (f *ManagedGroupContext) handleRemoveMemberEvent(rme RemoveMemberEvent) error {
	acl, err := CheckACL(f.OriginalMessage.Author, f.GroupConversation)
	if err == nil {
		if acl.Kick {
			f.Handler.HandleRemoveMember(f, rme)
			acl := f.GroupConversation.ACL
			delete(acl, rme.Handle)
			f.Handler.HandleACLUpdate(f, acl)
			return nil
		}
	}
	return fmt.Errorf("insufficient invite authorization")
}

// handleAddMemberEvent adds a group member to the conversation ACL
func (f *ManagedGroupContext) handleMemberList(mle MemberListEvent) error {
	if f.OriginalMessage.Author == f.GroupID {
		f.Handler.HandleMemberList(f, mle)
		return nil
	}
	return fmt.Errorf("insufficient invite authorization")
}

func (f *ManagedGroupContext) handleClearMessage(cme NewClearMessageEvent) error {
	log.Infof("New message from group %v", cme)
	acl, err := CheckACL(f.OriginalMessage.Author, f.GroupConversation)
	if err == nil {
		log.Infof("New message from group %v", cme)
		if acl.Append {
			f.Handler.HandleNewMessage(f, cme)
			return nil
		}
	}
	return fmt.Errorf("no permissions to append to the group")
}

func (f *ManagedGroupContext) handleChangeGroupMode(cgm ChangeGroupModeEvent) error {
	acl, err := CheckACL(f.OriginalMessage.Author, f.GroupConversation)
	if err == nil {
		if acl.ChangeAllChannelModes {
			f.Handler.HandleChangeGroupMode(f, cgm)
			return nil
		}
	}
	return fmt.Errorf("no permissions to append to the group")
}
