////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation                                   /
//                                                                             /
// All rights reserved.                                                        /
////////////////////////////////////////////////////////////////////////////////

//go:build js && wasm
// +build js,wasm

package indexedDb

import (
	"context"
	"encoding/json"
	"github.com/hack-pad/go-indexeddb/idb"
	"github.com/pkg/errors"
	jww "github.com/spf13/jwalterweatherman"
	"gitlab.com/elixxir/client/channels"
	"syscall/js"
	"time"

	"gitlab.com/elixxir/client/cmix/rounds"
	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
	cryptoChannel "gitlab.com/elixxir/crypto/channel"
	"gitlab.com/xx_network/primitives/id"
)

// jsObject is the Golang type translation for a JavaScript object
type jsObject map[string]interface{}

// wasmModel implements [channels.EventModel] interface which uses the channels
// system passed an object which adheres to in order to get events on the channel.
type wasmModel struct {
	db *idb.Database
}

// JoinChannel is called whenever a channel is joined locally.
func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) {
	parentErr := errors.New("failed to JoinChannel")

	// Build Channel object
	newChannel := Channel{
		Id:          channel.ReceptionID.Marshal(),
		Name:        channel.Name,
		Description: channel.Description,
	}

	// Convert Channel to jsObject
	newChannelJson, err := json.Marshal(&newChannel)
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Unable to marshal Channel: %+v", err))
		return
	}
	var channelObj *jsObject
	err = json.Unmarshal(newChannelJson, channelObj)
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Unable to unmarshal Channel: %+v", err))
		return
	}

	// Prepare the Transaction
	ctx := context.Background()
	txn, err := w.db.Transaction(idb.TransactionReadWrite, messageStoreName)
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Unable to create Transaction: %+v", err))
		return
	}
	store, err := txn.ObjectStore(messageStoreName)
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Unable to get ObjectStore: %+v", err))
		return
	}

	// Perform the operation
	_, err = store.Add(js.ValueOf(*channelObj))
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Unable to Add Channel: %+v", err))
		return
	}

	// Wait for the operation to return
	err = txn.Await(ctx)
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Adding Channel failed: %+v", err))
		return
	}
}

// LeaveChannel is called whenever a channel is left locally.
func (w *wasmModel) LeaveChannel(channelID *id.ID) {
	parentErr := errors.New("failed to LeaveChannel")

	// Prepare the Transaction
	ctx := context.Background()
	txn, err := w.db.Transaction(idb.TransactionReadWrite, messageStoreName)
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Unable to create Transaction: %+v", err))
		return
	}
	store, err := txn.ObjectStore(messageStoreName)
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Unable to get ObjectStore: %+v", err))
		return
	}

	// Perform the operation
	_, err = store.Delete(js.ValueOf(channelID.String()))
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Unable to Delete Channel: %+v", err))
		return
	}

	// Wait for the operation to return
	err = txn.Await(ctx)
	if err != nil {
		jww.ERROR.Printf("%+v", errors.Wrapf(parentErr,
			"Deleting Channel failed: %+v", err))
		return
	}
}

// ReceiveMessage is called whenever a message is received on a given channel
// It may be called multiple times on the same message, it is incumbent on
// the user of the API to filter such called by message ID.
func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID cryptoChannel.MessageID,
	senderUsername string, text string,
	timestamp time.Time, lease time.Duration, round rounds.Round) {
}

// ReceiveReply is called whenever a message is received which is a reply
// on a given channel. It may be called multiple times on the same message,
// it is incumbent on the user of the API to filter such called by message ID
// Messages may arrive our of order, so a reply in theory can arrive before
// the initial message, as a result it may be important to buffer replies.
func (w *wasmModel) ReceiveReply(ChannelID *id.ID, messageID cryptoChannel.MessageID,
	SenderUsername string, text string, timestamp time.Time,
	lease time.Duration, round rounds.Round) {

}

// ReceiveReaction is called whenever a reaction to a message is received
// on a given channel. It may be called multiple times on the same reaction,
// it is incumbent on the user of the API to filter such called by message ID
// Messages may arrive our of order, so a reply in theory can arrive before
// the initial message, as a result it may be important to buffer reactions.
func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID cryptoChannel.MessageID,
	reactionTo cryptoChannel.MessageID, senderUsername string,
	reaction string, timestamp time.Time, lease time.Duration,
	round rounds.Round) {
}

// MessageSent is called whenever the user sends a message. It should be
//designated as "sent" and that delivery is unknown.
func (w *wasmModel) MessageSent(channelID *id.ID, messageID cryptoChannel.MessageID,
	myUsername string, text string, timestamp time.Time,
	lease time.Duration, round rounds.Round) {

}

// ReplySent is called whenever the user sends a reply. It should be
// designated as "sent" and that delivery is unknown.
func (w *wasmModel) ReplySent(channelID *id.ID, messageID cryptoChannel.MessageID,
	replyTo cryptoChannel.MessageID, myUsername string, text string,
	timestamp time.Time, lease time.Duration, round rounds.Round) {

}

// ReactionSent is called whenever the user sends a reply. It should be
// designated as "sent" and that delivery is unknown.
func (w *wasmModel) ReactionSent(channelID *id.ID, messageID cryptoChannel.MessageID,
	reactionTo cryptoChannel.MessageID, senderUsername string,
	reaction string, timestamp time.Time, lease time.Duration,
	round rounds.Round) {

}

// UpdateSentStatus is called whenever the sent status of a message
// has changed
func (w *wasmModel) UpdateSentStatus(messageID cryptoChannel.MessageID,
	status channels.SentStatus) {

}