package metastore

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/dgraph-io/badger/v4"

	"warpbox/lib/helpers"
)

func AdminPermissions() TagPermissions {
	unlimited := int64(0)
	return TagPermissions{
		UploadAllowed:          true,
		MaxFileSizeBytes:       &unlimited,
		MaxBoxSizeBytes:        &unlimited,
		OneTimeDownloadAllowed: true,
		ZipDownloadAllowed:     true,
		RenewableAllowed:       true,
		AdminAccess:            true,
		AdminUsersManage:       true,
		AdminSettingsManage:    true,
		AdminBoxesView:         true,
	}
}

func (store *Store) EnsureAdminTag() (Tag, error) {
	tag, ok, err := store.GetTagByName(AdminTagName)
	if err != nil {
		return Tag{}, err
	}
	if ok {
		tag.Protected = true
		tag.Permissions = AdminPermissions()
		tag.Description = "Built-in administrator permissions"
		if err := store.UpdateTag(tag); err != nil {
			return Tag{}, err
		}
		return tag, nil
	}

	tag = Tag{
		Name:        AdminTagName,
		Description: "Built-in administrator permissions",
		Protected:   true,
		Permissions: AdminPermissions(),
	}
	if err := store.CreateTag(&tag); err != nil {
		return Tag{}, err
	}
	return tag, nil
}

func (store *Store) CreateTag(tag *Tag) error {
	if tag == nil {
		return fmt.Errorf("%w: tag cannot be nil", ErrInvalid)
	}
	tag.Name = strings.TrimSpace(tag.Name)
	tag.Description = strings.TrimSpace(tag.Description)
	if tag.Name == "" {
		return fmt.Errorf("%w: tag name cannot be empty", ErrInvalid)
	}

	now := time.Now().UTC()
	if tag.ID == "" {
		id, err := helpers.RandomHexID(16)
		if err != nil {
			return err
		}
		tag.ID = id
	}
	tag.CreatedAt = now
	tag.UpdatedAt = now
	normalizeTagPermissions(&tag.Permissions)

	return store.db.Update(func(txn *badger.Txn) error {
		if exists, err := keyExists(txn, tagNameKey(tag.Name)); err != nil || exists {
			if err != nil {
				return err
			}
			return fmt.Errorf("%w: tag name already exists", ErrDuplicate)
		}
		if err := putJSON(txn, tagKey(tag.ID), tag); err != nil {
			return err
		}
		return txn.Set(tagNameKey(tag.Name), []byte(tag.ID))
	})
}

func (store *Store) UpdateTag(tag Tag) error {
	tag.Name = strings.TrimSpace(tag.Name)
	tag.Description = strings.TrimSpace(tag.Description)
	if tag.ID == "" {
		return fmt.Errorf("%w: tag id cannot be empty", ErrInvalid)
	}
	if tag.Name == "" {
		return fmt.Errorf("%w: tag name cannot be empty", ErrInvalid)
	}
	tag.UpdatedAt = time.Now().UTC()
	normalizeTagPermissions(&tag.Permissions)

	return store.db.Update(func(txn *badger.Txn) error {
		var existing Tag
		if err := getJSON(txn, tagKey(tag.ID), &existing); err != nil {
			return err
		}
		if normalizeIndex(existing.Name) != normalizeIndex(tag.Name) {
			if exists, err := keyExists(txn, tagNameKey(tag.Name)); err != nil || exists {
				if err != nil {
					return err
				}
				return fmt.Errorf("%w: tag name already exists", ErrDuplicate)
			}
			if err := txn.Delete(tagNameKey(existing.Name)); err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
				return err
			}
			if err := txn.Set(tagNameKey(tag.Name), []byte(tag.ID)); err != nil {
				return err
			}
		}
		if existing.Protected {
			tag.Protected = true
		}
		if tag.Name == AdminTagName {
			tag.Protected = true
			tag.Permissions = AdminPermissions()
		}
		return putJSON(txn, tagKey(tag.ID), tag)
	})
}

func (store *Store) GetTag(id string) (Tag, bool, error) {
	var tag Tag
	err := store.db.View(func(txn *badger.Txn) error {
		return getJSON(txn, tagKey(id), &tag)
	})
	if errors.Is(err, ErrNotFound) {
		return Tag{}, false, nil
	}
	return tag, err == nil, err
}

func (store *Store) GetTagByName(name string) (Tag, bool, error) {
	var id string
	err := store.db.View(func(txn *badger.Txn) error {
		item, err := txn.Get(tagNameKey(name))
		if errors.Is(err, badger.ErrKeyNotFound) {
			return ErrNotFound
		}
		if err != nil {
			return err
		}
		return item.Value(func(data []byte) error {
			id = string(data)
			return nil
		})
	})
	if errors.Is(err, ErrNotFound) {
		return Tag{}, false, nil
	}
	if err != nil {
		return Tag{}, false, err
	}
	return store.GetTag(id)
}

func (store *Store) ListTags() ([]Tag, error) {
	tags := []Tag{}
	err := store.db.View(func(txn *badger.Txn) error {
		opts := badger.DefaultIteratorOptions
		opts.Prefix = []byte("tag/")
		it := txn.NewIterator(opts)
		defer it.Close()

		for it.Rewind(); it.Valid(); it.Next() {
			var tag Tag
			if err := it.Item().Value(func(data []byte) error {
				return json.Unmarshal(data, &tag)
			}); err != nil {
				return err
			}
			tags = append(tags, tag)
		}
		return nil
	})
	return tags, err
}

func (store *Store) TagsByID(ids []string) ([]Tag, error) {
	tags := make([]Tag, 0, len(ids))
	for _, id := range ids {
		tag, ok, err := store.GetTag(id)
		if err != nil {
			return nil, err
		}
		if ok {
			tags = append(tags, tag)
		}
	}
	return tags, nil
}

func normalizeTagPermissions(perms *TagPermissions) {
	if perms == nil {
		return
	}
	perms.AllowedExpirySeconds = uniqueInt64s(perms.AllowedExpirySeconds)
}

func tagKey(id string) []byte {
	return []byte("tag/" + strings.TrimSpace(id))
}

func tagNameKey(name string) []byte {
	return []byte("tag_by_name/" + normalizeIndex(name))
}
