package storage

import (
	"fmt"
	"time"

	"github.com/rs/zerolog/log"
	"gorm.io/gorm"
)

// Note represents an ActivityPub note
// ActivityPub notes can be quite a few things, depending on fields provided.
// A survey, a reply, a quote of another note, etc
// And depending on the origin server of a note, they are treated differently
// with for example rendering or available actions
// This struct attempts to contain all information necessary for easily working with a note
type Note struct {
	ID        string    `gorm:"primarykey"` // Make ID a string (uuid) for other implementations
	CreatedAt time.Time // When this entry was created
	UpdatedAt time.Time // When this entry was last updated
	// When this entry was deleted (for soft deletions)
	// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
	// If not null, this entry is marked as deleted
	DeletedAt gorm.DeletedAt `gorm:"index"`
	Creator   string         // Id of the author in the db, not the handle
	Remote    bool           // Whether the note is originally a remote one and just "cached"
	// Raw content of the note. So without additional formatting applied
	// Might already have formatting applied beforehand from the origin server
	RawContent     string
	ContentWarning *string    // Content warnings of the note, if it contains any
	Attachments    []string   `gorm:"serializer:json"` // Links to attachments
	Emotes         []string   `gorm:"serializer:json"` // Emotes used in that message
	RepliesTo      *string    // Url of the message this replies to
	Quotes         *string    // url of the message this note quotes
	Target         NoteTarget // Where to send this message to (public, home, followers, dm)
	Pings          []string   `gorm:"serializer:json"` // Who is being tagged in this message. Also serves as DM targets
	OriginServer   string     // Url of the origin server. Also the primary key for those
	Tags           []string   `gorm:"serializer:json"` // Hashtags
}

func (s *Storage) FindNoteById(id string) (*Note, error) {
	note := &Note{}
	cacheNote, err := s.cacheNoteIdToData(id)
	switch err {
	case nil:
		return cacheNote, nil
		// Empty case, not found in cache means check db
	case errCacheNotFound:
	default:
		return nil, err
	}
	switch err {

	}
	err = s.db.Find(note, id).Error
	switch err {
	case nil:
		if err = s.cache.Set(cacheNoteIdToNotePrefix+id, note); err != nil {
			log.Warn().Err(err).Str("note-id", id).Msg("Failed to place note in cache")
		}
		return note, nil
	case gorm.ErrRecordNotFound:
		return nil, ErrEntryNotFound
	default:
		return nil, err
	}
}

func (s *Storage) FindNotesByFuzzyContent(fuzzyContent string) ([]Note, error) {
	notes := []Note{}
	// TODO: Figure out if cache can be used here too
	err := s.db.Where("raw_content LIKE %?%", fuzzyContent).Find(notes).Error
	if err != nil {
		return nil, err
	}
	return notes, nil
}

func (s *Storage) FindNotesByAuthorHandle(handle string) ([]Note, error) {
	acc, err := s.FindAccountByFullHandle(handle)
	if err != nil {
		return nil, fmt.Errorf("account with handle %s not found: %w", handle, err)
	}
	return s.FindNotesByAuthorId(acc.ID)
}

func (s *Storage) FindNotesByAuthorId(id string) ([]Note, error) {
	notes := []Note{}
	err := s.db.Where("creator = ?", id).Find(notes).Error
	switch err {
	case nil:
		return notes, nil
	case gorm.ErrRecordNotFound:
		return nil, ErrEntryNotFound
	default:
		return nil, err
	}
}

func (s *Storage) UpdateNote(note *Note) error {
	if note == nil || note.ID == "" {
		return ErrInvalidData
	}
	err := s.db.Save(note).Error
	if err != nil {
		return err
	}
	err = s.cache.Set(cacheNoteIdToNotePrefix+note.ID, note)
	if err != nil {
		log.Warn().
			Err(err).
			Msg("Failed to update note into cache. Cache and db might be out of sync, a force sync is recommended")
	}
	return nil
}

func (s *Storage) CreateNote() (*Note, error) {
	// TODO: Think of good arguments and implement me
	panic("not implemented")
}

func (s *Storage) DeleteNote(id string) {
	s.cache.Delete(cacheNoteIdToNotePrefix + id)
	s.db.Delete(Note{ID: id})
}
