package hybrid

import (
	"crypto/ed25519"
	"encoding/base32"
	"encoding/json"
	"fmt"
	"strings"

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

const ManagedGroupOpen = "managed-group-open"

type GroupEventType int

const (
	MemberGroupIDKey   = "member_group_id_key"
	MemberMessageIDKey = "member_group_messge_id"
)

const (
	AddMember       = GroupEventType(0x1000)
	RemoveMember    = GroupEventType(0x2000)
	RotateKey       = GroupEventType(0x3000)
	NewMessage      = GroupEventType(0x4000)
	NewClearMessage = GroupEventType(0x5000)
	SyncRequest     = GroupEventType(0x6000)
)

type ManageGroupEvent struct {
	EventType GroupEventType `json:"t"`
	Data      string         `json:"d"` // json encoded data
}

type AddMemberEvent struct {
	Handle string `json:"h"`
}

type RemoveMemberEvent struct {
	Handle string `json:"h"`
}

type RotateKeyEvent struct {
	Key []byte `json:"k"`
}

type NewMessageEvent struct {
	EncryptedHybridGroupMessage []byte `json:"m"`
}

type NewClearMessageEvent struct {
	HybridGroupMessage HybridGroupMessage `json:"m"`
}

type SyncRequestMessage struct {
	// a map of MemberGroupID: MemberMessageID
	LastSeen map[int]int `json:"l"`
}

// This file contains code for the Hybrid Group / Managed Group types..
type HybridGroupMessage struct {
	Author          string `json:"a"` // the authors cwtch address
	MemberGroupID   uint32 `json:"g"`
	MemberMessageID uint32 `json:"m"`
	MessageBody     string `json:"b"`
	Sent            uint64 `json:"t"` // milliseconds since epoch
	Signature       []byte `json:"s"` // of json-encoded content (including empty sig)
}

// AuthenticateMessage returns true if the Author of the message produced the Signature over the message
func AuthenticateMessage(message HybridGroupMessage) bool {
	messageCopy := message
	messageCopy.Signature = []byte{}
	// Otherwise we derive the public key from the sender and check it against that.
	decodedPub, err := base32.StdEncoding.DecodeString(strings.ToUpper(message.Author))
	if err == nil {
		data, err := json.Marshal(messageCopy)
		if err == nil && len(decodedPub) >= 32 {
			return ed25519.Verify(decodedPub[:32], data, message.Signature)
		}
	}
	log.Errorf("invalid signature on message from %s", message)

	return false
}

func CheckACL(handle string, group *model.Conversation) (*model.AccessControl, error) {
	if isOpen, exists := group.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ConversationZone.ConstructZonedPath(ManagedGroupOpen)).ToString()]; !exists {
		return nil, fmt.Errorf("group has not been setup correctly - ManagedGroupOpen does not exist ")
	} else if isOpen == event.True {
		// We don't need to do a membership check
		defaultACL := group.GetPeerAC()
		return &defaultACL, nil
	}
	// If this is a closed group. Check if we have an ACL entry for this member
	// If we don't OR that member has been blocked, then close the connection.
	if acl, inGroup := group.ACL[handle]; !inGroup || acl.Blocked {
		log.Infof("ACL Check Failed: %v %v %v", handle, acl, inGroup)
		return nil, fmt.Errorf("peer is not a member of this group")
	} else {
		return &acl, nil
	}
}
