package metastore

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

	"github.com/dgraph-io/badger/v4"
	"golang.org/x/crypto/bcrypt"

	"warpbox/lib/helpers"
)

var (
	ErrNotFound  = errors.New("not found")
	ErrDuplicate = errors.New("duplicate")
	ErrInvalid   = errors.New("invalid")
)

type Store struct {
	db *badger.DB
}

func Open(path string) (*Store, error) {
	opts := badger.DefaultOptions(path).WithLogger(nil)
	db, err := badger.Open(opts)
	if err != nil {
		return nil, err
	}
	return &Store{db: db}, nil
}

func (store *Store) Close() error {
	if store == nil || store.db == nil {
		return nil
	}
	return store.db.Close()
}

func (store *Store) SetSetting(name string, value string) error {
	name = strings.TrimSpace(name)
	if name == "" {
		return fmt.Errorf("%w: setting name cannot be empty", ErrInvalid)
	}

	return store.db.Update(func(txn *badger.Txn) error {
		return txn.Set(settingKey(name), []byte(value))
	})
}

func (store *Store) DeleteSetting(name string) error {
	return store.db.Update(func(txn *badger.Txn) error {
		return txn.Delete(settingKey(name))
	})
}

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

func (store *Store) ListSettings() (map[string]string, error) {
	settings := make(map[string]string)
	err := store.db.View(func(txn *badger.Txn) error {
		opts := badger.DefaultIteratorOptions
		opts.Prefix = []byte("setting/")
		it := txn.NewIterator(opts)
		defer it.Close()

		for it.Rewind(); it.Valid(); it.Next() {
			item := it.Item()
			name := strings.TrimPrefix(string(item.Key()), "setting/")
			if err := item.Value(func(data []byte) error {
				settings[name] = string(data)
				return nil
			}); err != nil {
				return err
			}
		}
		return nil
	})
	return settings, err
}

func HashPassword(password string) (string, error) {
	if strings.TrimSpace(password) == "" {
		return "", fmt.Errorf("%w: password cannot be empty", ErrInvalid)
	}
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	return string(hash), nil
}

func VerifyPassword(hash string, password string) bool {
	if hash == "" || password == "" {
		return false
	}
	return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}

func (store *Store) CreateUserWithPassword(username string, email string, password string, tagIDs []string) (User, error) {
	hash, err := HashPassword(password)
	if err != nil {
		return User{}, err
	}
	user := User{
		Username:     username,
		Email:        email,
		PasswordHash: hash,
		TagIDs:       uniqueStrings(tagIDs),
	}
	if err := store.CreateUser(&user); err != nil {
		return User{}, err
	}
	return user, nil
}

func (store *Store) CreateUser(user *User) error {
	if user == nil {
		return fmt.Errorf("%w: user cannot be nil", ErrInvalid)
	}
	username := strings.TrimSpace(user.Username)
	if username == "" {
		return fmt.Errorf("%w: username cannot be empty", ErrInvalid)
	}
	email := strings.TrimSpace(user.Email)
	if user.PasswordHash == "" {
		return fmt.Errorf("%w: password hash cannot be empty", ErrInvalid)
	}

	now := time.Now().UTC()
	if user.ID == "" {
		id, err := helpers.RandomHexID(16)
		if err != nil {
			return err
		}
		user.ID = id
	}
	user.Username = username
	user.Email = email
	user.TagIDs = uniqueStrings(user.TagIDs)
	user.CreatedAt = now
	user.UpdatedAt = now

	return store.db.Update(func(txn *badger.Txn) error {
		if exists, err := keyExists(txn, usernameKey(username)); err != nil || exists {
			if err != nil {
				return err
			}
			return fmt.Errorf("%w: username already exists", ErrDuplicate)
		}
		if email != "" {
			if exists, err := keyExists(txn, emailKey(email)); err != nil || exists {
				if err != nil {
					return err
				}
				return fmt.Errorf("%w: email already exists", ErrDuplicate)
			}
		}
		if err := putJSON(txn, userKey(user.ID), user); err != nil {
			return err
		}
		if err := txn.Set(usernameKey(username), []byte(user.ID)); err != nil {
			return err
		}
		if email != "" {
			return txn.Set(emailKey(email), []byte(user.ID))
		}
		return nil
	})
}

func (store *Store) UpdateUser(user User) error {
	if strings.TrimSpace(user.ID) == "" {
		return fmt.Errorf("%w: user id cannot be empty", ErrInvalid)
	}
	user.Username = strings.TrimSpace(user.Username)
	user.Email = strings.TrimSpace(user.Email)
	if user.Username == "" {
		return fmt.Errorf("%w: username cannot be empty", ErrInvalid)
	}
	user.TagIDs = uniqueStrings(user.TagIDs)
	user.UpdatedAt = time.Now().UTC()

	return store.db.Update(func(txn *badger.Txn) error {
		var existing User
		if err := getJSON(txn, userKey(user.ID), &existing); err != nil {
			return err
		}

		oldUsername := normalizeIndex(existing.Username)
		newUsername := normalizeIndex(user.Username)
		if oldUsername != newUsername {
			if exists, err := keyExists(txn, usernameKey(user.Username)); err != nil || exists {
				if err != nil {
					return err
				}
				return fmt.Errorf("%w: username already exists", ErrDuplicate)
			}
			if err := txn.Delete(usernameKey(existing.Username)); err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
				return err
			}
			if err := txn.Set(usernameKey(user.Username), []byte(user.ID)); err != nil {
				return err
			}
		}

		oldEmail := normalizeIndex(existing.Email)
		newEmail := normalizeIndex(user.Email)
		if oldEmail != newEmail {
			if newEmail != "" {
				if exists, err := keyExists(txn, emailKey(user.Email)); err != nil || exists {
					if err != nil {
						return err
					}
					return fmt.Errorf("%w: email already exists", ErrDuplicate)
				}
				if err := txn.Set(emailKey(user.Email), []byte(user.ID)); err != nil {
					return err
				}
			}
			if oldEmail != "" {
				if err := txn.Delete(emailKey(existing.Email)); err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
					return err
				}
			}
		}

		return putJSON(txn, userKey(user.ID), user)
	})
}

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

func (store *Store) GetUserByUsername(username string) (User, bool, error) {
	return store.getUserByIndex(usernameKey(username))
}

func (store *Store) GetUserByEmail(email string) (User, bool, error) {
	return store.getUserByIndex(emailKey(email))
}

func (store *Store) ListUsers() ([]User, error) {
	users := []User{}
	err := store.db.View(func(txn *badger.Txn) error {
		opts := badger.DefaultIteratorOptions
		opts.Prefix = []byte("user/")
		it := txn.NewIterator(opts)
		defer it.Close()

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

func (store *Store) getUserByIndex(key []byte) (User, bool, error) {
	var id string
	err := store.db.View(func(txn *badger.Txn) error {
		item, err := txn.Get(key)
		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 User{}, false, nil
	}
	if err != nil {
		return User{}, false, err
	}
	return store.GetUser(id)
}

func putJSON(txn *badger.Txn, key []byte, value any) error {
	data, err := json.Marshal(value)
	if err != nil {
		return err
	}
	return txn.Set(key, data)
}

func getJSON(txn *badger.Txn, key []byte, value any) error {
	item, err := txn.Get(key)
	if errors.Is(err, badger.ErrKeyNotFound) {
		return ErrNotFound
	}
	if err != nil {
		return err
	}
	return item.Value(func(data []byte) error {
		return json.Unmarshal(data, value)
	})
}

func keyExists(txn *badger.Txn, key []byte) (bool, error) {
	_, err := txn.Get(key)
	if errors.Is(err, badger.ErrKeyNotFound) {
		return false, nil
	}
	return err == nil, err
}

func settingKey(name string) []byte {
	return []byte("setting/" + strings.TrimSpace(name))
}

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

func usernameKey(username string) []byte {
	return []byte("user_by_name/" + normalizeIndex(username))
}

func emailKey(email string) []byte {
	return []byte("user_by_email/" + normalizeIndex(email))
}

func normalizeIndex(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func uniqueStrings(values []string) []string {
	seen := make(map[string]bool, len(values))
	out := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" || seen[value] {
			continue
		}
		seen[value] = true
		out = append(out, value)
	}
	return out
}
