package extensions

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"
	"git.openprivacy.ca/openprivacy/log"
	"strconv"
)

// ProfileValueExtension implements custom Profile Names over Cwtch
type ProfileValueExtension struct {
}

func (pne ProfileValueExtension) NotifySettingsUpdate(_ settings.GlobalSettings) {
}

func (pne ProfileValueExtension) EventsToRegister() []event.Type {
	return []event.Type{event.PeerStateChange, event.Heartbeat}
}

func (pne ProfileValueExtension) ExperimentsToRegister() []string {
	return nil
}

func (pne ProfileValueExtension) requestProfileInfo(profile peer.CwtchPeer, ci *model.Conversation) {
	profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.Name)
	profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.ProfileStatus)
	profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute1)
	profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute2)
	profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute3)
}

func (pne ProfileValueExtension) OnEvent(ev event.Event, profile peer.CwtchPeer) {
	switch ev.EventType {
	case event.Heartbeat:
		// once every heartbeat, loop through conversations and, if they are online, request an update to any long info..
		conversations, err := profile.FetchConversations()
		if err == nil {
			for _, ci := range conversations {
				if profile.GetPeerState(ci.Handle) == connections.AUTHENTICATED {
					pne.requestProfileInfo(profile, ci)
				}
			}
		}
	case event.PeerStateChange:
		ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
		if err == nil {
			// if we have re-authenticated with thie peer then request their profile info...
			if connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] == connections.AUTHENTICATED {
				// Request some profile information...
				pne.requestProfileInfo(profile, ci)
			}
		}
	}
}

// OnContactReceiveValue for ProfileValueExtension handles saving specific Public Profile Values like Profile Name
func (pne ProfileValueExtension) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, szp attr.ScopedZonedPath, value string, exists bool) {
	// Allow public profile parameters to be added as contact specific attributes...
	scope, zone, _ := szp.GetScopeZonePath()
	if exists && scope.IsPublic() && zone == attr.ProfileZone {

		// Check the current value of the attribute
		currentValue, err := profile.GetConversationAttribute(conversation.ID, szp)

		// Note: the Profile Value Extension is in charge of sending value updates to the
		// UI. We filter these updates as we receive duplicates (thanks to reconnections, heartbeats and other processes)
		// However, there are a set of values whose state can change even though the value does not e.g. profile images
		// which can be in various states (downloading, downloaded, missing etc.)
		if err == nil && currentValue == value {
			// Value exists and the value is the same, short-circuit
			return
		}

		// Save the new Attribute
		err = profile.SetConversationAttribute(conversation.ID, szp, value)
		if err != nil {
			// Something else wen't wrong.. short-circuit
			log.Errorf("error setting conversation attribute %v", err)
			return
		}

		// Finally publish an update for listeners to react to.
		scope, zone, zpath := szp.GetScopeZonePath()
		profile.PublishEvent(event.NewEvent(event.UpdatedConversationAttribute, map[event.Field]string{
			event.Scope:          string(scope),
			event.Path:           string(zone.ConstructZonedPath(zpath)),
			event.Data:           value,
			event.RemotePeer:     conversation.Handle,
			event.ConversationID: strconv.Itoa(conversation.ID),
		}))
	}
}

// OnContactRequestValue for ProfileValueExtension handles returning Public Profile Values
func (pne ProfileValueExtension) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, szp attr.ScopedZonedPath) {
	if acl, exists := conversation.ACL[conversation.Handle]; !exists || !acl.ExchangeAttributes {
		return
	}

	scope, zone, zpath := szp.GetScopeZonePath()
	log.Debugf("Looking up public | conversation scope/zone %v", szp.ToString())
	if scope.IsPublic() || scope.IsConversation() {
		val, exists := profile.GetScopedZonedAttribute(scope, zone, zpath)

		// NOTE: Temporary Override because UI currently wipes names if it can't find them...
		if !exists && zone == attr.UnknownZone && zpath == constants.Name {
			val, exists = profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
		}

		// NOTE: Cwtch 1.15+ requires that profiles be able to restrict file downloading to specific contacts. As such we need an ACL check here
		// on the fileshareing zone.
		// TODO: Split this functionality into FilesharingFunctionality, and restrict this function to only considering Profile zoned attributes?
		if zone == attr.FilesharingZone {
			if !conversation.GetPeerAC().ShareFiles {
				return
			}
		}

		// Construct a Response
		resp := event.NewEvent(event.SendRetValMessageToPeer, map[event.Field]string{event.ConversationID: strconv.Itoa(conversation.ID), event.RemotePeer: conversation.Handle, event.Exists: strconv.FormatBool(exists)})
		resp.EventID = eventID
		if exists {
			resp.Data[event.Data] = val
		} else {
			resp.Data[event.Data] = ""
		}

		log.Debugf("Responding with SendRetValMessageToPeer exists:%v data: %v\n", exists, val)
		profile.PublishEvent(resp)
	}
}
