package filesharing

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

type ImagePreviewsFunctionality struct {
	downloadFolder string
}

func (i *ImagePreviewsFunctionality) NotifySettingsUpdate(settings settings.GlobalSettings) {
	i.downloadFolder = settings.DownloadPath
}

func (i *ImagePreviewsFunctionality) EventsToRegister() []event.Type {
	return []event.Type{event.ProtocolEngineCreated, event.NewMessageFromPeer, event.NewMessageFromGroup, event.PeerStateChange, event.Heartbeat, event.FileDownloaded}
}

func (i *ImagePreviewsFunctionality) ExperimentsToRegister() []string {
	return []string{constants.FileSharingExperiment, constants.ImagePreviewsExperiment}
}

func (i *ImagePreviewsFunctionality) OnEvent(ev event.Event, profile peer.CwtchPeer) {
	if profile.IsFeatureEnabled(constants.FileSharingExperiment) && profile.IsFeatureEnabled(constants.ImagePreviewsExperiment) {
		switch ev.EventType {
		case event.NewMessageFromPeer:
			ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
			if err == nil {
				if ci.GetPeerAC().RenderImages {
					i.handleImagePreviews(profile, &ev, ci.ID, ci.ID)
				}
			}
		case event.NewMessageFromGroup:
			ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
			if err == nil {
				if ci.GetPeerAC().RenderImages {
					i.handleImagePreviews(profile, &ev, ci.ID, ci.ID)
				}
			}
		case event.PeerStateChange:
			ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
			if err == nil {
				// if we have re-authenticated with this peer then request their profile image...
				if connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] == connections.AUTHENTICATED {
					profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
				}
			}
		case event.Heartbeat:
			conversations, err := profile.FetchConversations()
			if err == nil {
				for _, ci := range conversations {
					if profile.GetPeerState(ci.Handle) == connections.AUTHENTICATED {
						// if we have enabled file shares for this contact, then send them our profile image
						// NOTE: In the past, Cwtch treated "profile image" as a public file share. As such, anyone with the file key and who is able
						// to authenticate with the profile (i.e. non-blocked peers) can download the file (if the global profile images experiment is enabled)
						// To better allow for fine-grained permissions (and to support hybrid group permissions), we want to enable per-conversation file
						// sharing permissions. As such, profile images are now only shared with contacts with that permission enabled.
						// (i.e. all previous accepted contacts, new accepted contacts, and contacts who have this toggle set explictly)
						if ci.GetPeerAC().ShareFiles {
							profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
						}
					}
				}
			}
		case event.ProtocolEngineCreated:
			// Now that the Peer Engine is Activated, Reshare Profile Images
			key, exists := profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
			if exists {
				serializedManifest, _ := profile.GetScopedZonedAttribute(attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", key))
				// reset the share timestamp, currently file shares are hardcoded to expire after 30 days...
				// we reset the profile image here so that it is always available.
				profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.ts", key), strconv.FormatInt(time.Now().Unix(), 10))
				log.Debugf("Custom Profile Image: %v %s", key, serializedManifest)
				f := Functionality{}
				f.RestartFileShare(profile, key)
			}
		case event.FileDownloaded:
			ci, err := profile.FetchConversationInfo(ev.Data[event.RemotePeer])
			if err == nil {

				fileKey := ev.Data[event.FileKey]
				if profileImageFileKey, exists := ci.GetAttribute(attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey); exists && fileKey == profileImageFileKey {
					profile.SetConversationAttribute(
						ci.ID,
						attr.LocalScope.ConstructScopedZonedPath(attr.FilesharingZone.ConstructZonedPath(constants.CustomProfileImagePath)),
						ev.Data[event.FilePath],
					)
					profile.PublishEvent(event.NewEvent(event.UpdatedConversationAttribute, map[event.Field]string{
						event.Scope:          string(attr.LocalScope),
						event.Path:           string(attr.FilesharingZone.ConstructZonedPath(constants.CustomProfileImagePath)),
						event.Data:           ev.Data[event.FilePath],
						event.RemotePeer:     ci.Handle,
						event.ConversationID: strconv.Itoa(ci.ID),
					}))
				}
			}
		}
	}
}

func (i *ImagePreviewsFunctionality) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, path attr.ScopedZonedPath) {
}

func (i *ImagePreviewsFunctionality) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, path attr.ScopedZonedPath, value string, exists bool) {
	if profile.IsFeatureEnabled(constants.FileSharingExperiment) && profile.IsFeatureEnabled(constants.ImagePreviewsExperiment) {
		_, zone, path := path.GetScopeZonePath()
		if exists && zone == attr.ProfileZone && path == constants.CustomProfileImageKey {
			// We only download from accepted conversations
			if conversation.GetPeerAC().RenderImages {
				fileKey := value
				basepath := i.downloadFolder
				fsf := FunctionalityGate()
				// We always overwrite profile image files...
				fp, mp := GenerateDownloadPath(basepath, fileKey, true)

				// If we have marked this file as complete...
				if value, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey)); exists && value == event.True {
					if _, err := os.Stat(fp); err == nil {
						// file is marked as completed downloaded and exists...
						// Note: this will also resend the FileDownloaded event if successful...
						if fsf.VerifyOrResumeDownload(profile, conversation.ID, fileKey, constants.ImagePreviewMaxSizeInBytes) == nil {
							return
						}
						// Otherwise we fall through...
					}
					// Something went wrong...the file is marked as complete but either doesn't exist, or is corrupted such that we can't continue...
					// So mark complete as false...
					profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey), event.False)
				}

				// If we have reached this point then we need to download the file again...
				log.Debugf("Downloading Profile Image %v %v %v", fp, mp, fileKey)
				fsf.DownloadFile(profile, conversation.ID, fp, mp, fileKey, constants.ImagePreviewMaxSizeInBytes)
			}
		}
	}
}

// handleImagePreviews checks settings and, if appropriate, auto-downloads any images
func (i *ImagePreviewsFunctionality) handleImagePreviews(profile peer.CwtchPeer, ev *event.Event, conversationID, senderID int) {
	if profile.IsFeatureEnabled(constants.FileSharingExperiment) && profile.IsFeatureEnabled(constants.ImagePreviewsExperiment) {
		ci, err := profile.GetConversationInfo(senderID)
		if err != nil {
			log.Errorf("attempted to call handleImagePreviews with unknown conversation: %v", senderID)
			return
		}

		if !ci.GetPeerAC().ShareFiles || !ci.GetPeerAC().RenderImages {
			log.Infof("refusing to autodownload files from sender: %v. conversation AC does not permit image rendering", senderID)
			return
		}

		// Short-circuit failures
		// Don't auto-download images if the download path does not exist.
		if i.downloadFolder == "" {
			log.Errorf("download folder %v is not set", i.downloadFolder)
			return
		}

		// Don't auto-download images if the download path does not exist.
		if _, err := os.Stat(i.downloadFolder); os.IsNotExist(err) {
			log.Errorf("download folder %v does not exist", i.downloadFolder)
			return
		}

		// If file sharing is enabled then reshare all active files...
		fsf := FunctionalityGate()

		// Now look at the image preview experiment
		var cm model.MessageWrapper
		err = json.Unmarshal([]byte(ev.Data[event.Data]), &cm)
		if err == nil && cm.Overlay == model.OverlayFileSharing {
			log.Debugf("Received File Sharing Message")
			var fm OverlayMessage
			err = json.Unmarshal([]byte(cm.Data), &fm)
			if err == nil {
				if fm.ShouldAutoDL() {
					basepath := i.downloadFolder
					fp, mp := GenerateDownloadPath(basepath, fm.Name, false)
					log.Debugf("autodownloading file! %v %v %v", basepath, fp, i.downloadFolder)
					ev.Data["Auto"] = constants.True
					mID, _ := strconv.Atoi(ev.Data["Index"])
					profile.UpdateMessageAttribute(conversationID, 0, mID, constants.AttrDownloaded, constants.True)
					fsf.DownloadFile(profile, senderID, fp, mp, fm.FileKey(), constants.ImagePreviewMaxSizeInBytes)
				}
			}
		}
	}
}
