package servers

import (
	"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/protocol/connections"
	"cwtch.im/cwtch/settings"
	"encoding/json"
	"errors"
	"git.openprivacy.ca/openprivacy/log"
)

const (
	// ServerList is a json encoded list of servers
	ServerList = event.Field("ServerList")
)

const (
	// UpdateServerInfo is an event containing a ProfileOnion and a ServerList
	UpdateServerInfo = event.Type("UpdateServerInfo")
)

// Functionality groups some common UI triggered functions for contacts...
type Functionality struct {
}

func (f *Functionality) NotifySettingsUpdate(settings settings.GlobalSettings) {

}

func (f *Functionality) EventsToRegister() []event.Type {
	return []event.Type{event.QueueJoinServer}
}

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

// OnEvent handles File Sharing Hooks like Manifest Received and FileDownloaded
func (f *Functionality) OnEvent(ev event.Event, profile peer.CwtchPeer) {
	if profile.IsFeatureEnabled(constants.GroupsExperiment) {
		switch ev.EventType {
		// keep the UI in sync with the current backend server updates...
		// queue join server gets triggered on load and on new servers so it's a nice
		// low-noise event to hook into...
		case event.QueueJoinServer:
			f.PublishServerUpdate(profile)
		}
	}
}

func (f *Functionality) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, path attr.ScopedZonedPath) {
	// nop
}

func (f *Functionality) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, path attr.ScopedZonedPath, value string, exists bool) {
	// nopt
}

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

// ServerKey packages up key information...
// TODO: Can this be merged with KeyBundle?
type ServerKey struct {
	Type string `json:"type"`
	Key  string `json:"key"`
}

// SyncStatus packages up server sync information...
type SyncStatus struct {
	StartTime       string `json:"startTime"`
	LastMessageTime string `json:"lastMessageTime"`
}

// Server encapsulates the information needed to represent a server...
type Server struct {
	Onion        string      `json:"onion"`
	Identifier   int         `json:"identifier"`
	Status       string      `json:"status"`
	Description  string      `json:"description"`
	Keys         []ServerKey `json:"keys"`
	SyncProgress SyncStatus  `json:"syncProgress"`
}

// PublishServerUpdate serializes the current list of group servers and publishes an event with this information
func (f *Functionality) PublishServerUpdate(profile peer.CwtchPeer) error {
	serverListForOnion := f.GetServerInfoList(profile)
	serversListBytes, err := json.Marshal(serverListForOnion)
	profile.PublishEvent(event.NewEvent(UpdateServerInfo, map[event.Field]string{"ProfileOnion": profile.GetOnion(), ServerList: string(serversListBytes)}))
	return err
}

// GetServerInfoList compiles all the information the UI might need regarding all servers..
func (f *Functionality) GetServerInfoList(profile peer.CwtchPeer) []Server {
	var servers []Server
	for _, server := range profile.GetServers() {
		server, err := f.GetServerInfo(profile, server)
		if err != nil {
			log.Errorf("profile server list is corrupted: %v", err)
			continue
		}
		servers = append(servers, server)
	}
	return servers
}

// DeleteServer purges a server and all related keys from a profile
func (f *Functionality) DeleteServerInfo(profile peer.CwtchPeer, serverOnion string) error {
	// Servers are stores as special conversations
	ci, err := profile.FetchConversationInfo(serverOnion)
	if err != nil {
		return err
	}
	// Purge keys...
	// NOTE: This will leave some groups in the state of being unable to connect to a particular
	// server.
	profile.DeleteConversation(ci.ID)
	f.PublishServerUpdate(profile)
	return nil
}

// GetServerInfo compiles all the information the UI might need regarding a particular server including any verified
// cryptographic keys
func (f *Functionality) GetServerInfo(profile peer.CwtchPeer, serverOnion string) (Server, error) {
	serverInfo, err := profile.FetchConversationInfo(serverOnion)
	if err != nil {
		return Server{}, errors.New("server not found")
	}
	keyTypes := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass}
	var serverKeys []ServerKey

	for _, keyType := range keyTypes {
		if key, has := serverInfo.GetAttribute(attr.PublicScope, attr.ServerKeyZone, string(keyType)); has {
			serverKeys = append(serverKeys, ServerKey{Type: string(keyType), Key: key})
		}
	}

	description, _ := serverInfo.GetAttribute(attr.LocalScope, attr.ServerZone, constants.Description)
	startTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.SyncPreLastMessageTime)).ToString()]
	recentTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.SyncMostRecentMessageTime)).ToString()]
	syncStatus := SyncStatus{startTimeStr, recentTimeStr}

	return Server{Onion: serverOnion, Identifier: serverInfo.ID, Status: connections.ConnectionStateName[profile.GetPeerState(serverInfo.Handle)], Keys: serverKeys, Description: description, SyncProgress: syncStatus}, nil
}
