package arn

import (
	"reflect"
	"sort"

	"github.com/aerogo/nano"
)

// EditLogEntry is an entry in the editor log.
type EditLogEntry struct {
	UserID     UserID `json:"userId"`
	Action     string `json:"action"`
	ObjectID   ID     `json:"objectId"`   // The ID of what was edited
	ObjectType string `json:"objectType"` // The typename of what was edited
	Key        string `json:"key"`
	OldValue   string `json:"oldValue"`
	NewValue   string `json:"newValue"`
	Created    string `json:"created"`

	hasID
}

// NewEditLogEntry creates a new edit log entry.
func NewEditLogEntry(userID UserID, action string, objectType string, objectID ID, key string, oldValue string, newValue string) *EditLogEntry {
	return &EditLogEntry{
		hasID: hasID{
			ID: GenerateID("EditLogEntry"),
		},
		UserID:     userID,
		Action:     action,
		ObjectType: objectType,
		ObjectID:   objectID,
		Key:        key,
		OldValue:   oldValue,
		NewValue:   newValue,
		Created:    DateTimeUTC(),
	}
}

// User returns the user the log entry belongs to.
func (entry *EditLogEntry) User() *User {
	user, _ := GetUser(entry.UserID)
	return user
}

// Object returns the object the log entry refers to.
func (entry *EditLogEntry) Object() interface{} {
	obj, _ := DB.Get(entry.ObjectType, entry.ObjectID)
	return obj
}

// EditorScore returns the editing score for this log entry.
func (entry *EditLogEntry) EditorScore() int {
	switch entry.Action {
	case "create":
		obj, err := DB.Get(entry.ObjectType, entry.ObjectID)

		if err != nil {
			return 0
		}

		v := reflect.Indirect(reflect.ValueOf(obj))
		isDraft := v.FieldByName("IsDraft")

		if isDraft.Kind() == reflect.Bool && isDraft.Bool() {
			// No score for drafts
			return 0
		}

		return 4

	case "edit":
		score := 4

		// Bonus score for editing anime
		if entry.ObjectType == "Anime" {
			score++

			// Bonus score for editing anime synopsis
			if entry.Key == "Summary" || entry.Key == "Synopsis" {
				score++
			}
		}

		return score

	case "delete", "arrayRemove":
		return 3

	case "arrayAppend":
		return 0
	}

	return 0
}

// ActionHumanReadable returns the human readable version of the action.
func (entry *EditLogEntry) ActionHumanReadable() string {
	switch entry.Action {
	case "create":
		return "Created"

	case "edit":
		return "Edited"

	case "delete":
		return "Deleted"

	case "arrayAppend":
		return "Added an element"

	case "arrayRemove":
		return "Removed an element"

	default:
		return entry.Action
	}
}

// StreamEditLogEntries returns a stream of all log entries.
func StreamEditLogEntries() <-chan *EditLogEntry {
	channel := make(chan *EditLogEntry, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("EditLogEntry") {
			channel <- obj.(*EditLogEntry)
		}

		close(channel)
	}()

	return channel
}

// AllEditLogEntries returns a slice of all log entries.
func AllEditLogEntries() []*EditLogEntry {
	all := make([]*EditLogEntry, 0, DB.Collection("EditLogEntry").Count())

	stream := StreamEditLogEntries()

	for obj := range stream {
		all = append(all, obj)
	}

	return all
}

// FilterEditLogEntries filters all log entries by a custom function.
func FilterEditLogEntries(filter func(*EditLogEntry) bool) []*EditLogEntry {
	var filtered []*EditLogEntry

	channel := DB.All("EditLogEntry")

	for obj := range channel {
		realObject := obj.(*EditLogEntry)

		if filter(realObject) {
			filtered = append(filtered, realObject)
		}
	}

	return filtered
}

// SortEditLogEntriesLatestFirst puts the latest entries on top.
func SortEditLogEntriesLatestFirst(entries []*EditLogEntry) {
	sort.Slice(entries, func(i, j int) bool {
		return entries[i].Created > entries[j].Created
	})
}