package storage

import (
	"fmt"
	"sort"
	"time"

	"github.com/teris-io/shortid"
	bolt "go.etcd.io/bbolt"
)

func (s *Storage) CreateEntry(category, title, channelLink, directLink string) (*Entry, error) {
	entry := &Entry{
		ID:          shortid.MustGenerate(),
		Category:    category,
		Title:       title,
		Link:        channelLink,
		ChannelLink: channelLink,
		DirectLink:  directLink,
		Timestamp:   time.Now(),
	}

	err := s.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketEntries)
		data, err := encodeJSON(entry)
		if err != nil {
			return err
		}
		return b.Put([]byte(entry.ID), data)
	})

	if err != nil {
		return nil, err
	}
	return entry, nil
}

func (s *Storage) GetEntry(id string) (*Entry, error) {
	var entry *Entry
	err := s.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketEntries)
		data := b.Get([]byte(id))
		if data == nil {
			return fmt.Errorf("entry %q not found", id)
		}
		entry = &Entry{}
		return decodeJSON(data, entry)
	})
	return entry, err
}

func (s *Storage) DeleteEntry(id string) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketEntries)
		if b.Get([]byte(id)) == nil {
			return fmt.Errorf("entry %q not found", id)
		}
		return b.Delete([]byte(id))
	})
}

func (s *Storage) UpdateEntryTitle(id, title string) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketEntries)
		data := b.Get([]byte(id))
		if data == nil {
			return fmt.Errorf("entry %q not found", id)
		}

		var entry Entry
		if err := decodeJSON(data, &entry); err != nil {
			return err
		}

		entry.Title = title
		newData, err := encodeJSON(entry)
		if err != nil {
			return err
		}

		return b.Put([]byte(id), newData)
	})
}

func (s *Storage) UpdateEntryCategory(id, category string) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketEntries)
		data := b.Get([]byte(id))
		if data == nil {
			return fmt.Errorf("entry %q not found", id)
		}

		var entry Entry
		if err := decodeJSON(data, &entry); err != nil {
			return err
		}

		entry.Category = category
		newData, err := encodeJSON(entry)
		if err != nil {
			return err
		}

		return b.Put([]byte(id), newData)
	})
}

func (s *Storage) ListEntries(category string) ([]Entry, error) {
	var entries []Entry
	err := s.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketEntries)
		return b.ForEach(func(k, v []byte) error {
			var entry Entry
			if err := decodeJSON(v, &entry); err != nil {
				return err
			}
			if category == "" || entry.Category == category {
				entries = append(entries, entry)
			}
			return nil
		})
	})
	if err != nil {
		return nil, err
	}

	sort.Slice(entries, func(i, j int) bool {
		return entries[i].Timestamp.Before(entries[j].Timestamp)
	})

	return entries, nil
}

func (s *Storage) ListAllEntries() ([]Entry, error) {
	return s.ListEntries("")
}

func (s *Storage) GetEntriesByCategory() (map[string][]Entry, error) {
	entries, err := s.ListAllEntries()
	if err != nil {
		return nil, err
	}

	result := make(map[string][]Entry)
	for _, e := range entries {
		result[e.Category] = append(result[e.Category], e)
	}
	return result, nil
}
