package bolt

import (
	"context"

	"github.com/boltdb/bolt"
	"github.com/influxdata/chronograf"
	"github.com/influxdata/chronograf/bolt/internal"
)

// Ensure UsersStore implements chronograf.UsersStore.
var _ chronograf.UsersStore = &UsersStore{}

// UsersBucket is used to store users local to chronograf
var UsersBucket = []byte("UsersV1")

// UsersStore uses bolt to store and retrieve users
type UsersStore struct {
	client *Client
}

// get searches the UsersStore for user with name and returns the bolt representation
func (s *UsersStore) get(ctx context.Context, name string) (*internal.User, error) {
	found := false
	var user internal.User
	err := s.client.db.View(func(tx *bolt.Tx) error {
		err := tx.Bucket(UsersBucket).ForEach(func(k, v []byte) error {
			var u chronograf.User
			if err := internal.UnmarshalUser(v, &u); err != nil {
				return err
			} else if u.Name != name {
				return nil
			}
			found = true
			if err := internal.UnmarshalUserPB(v, &user); err != nil {
				return err
			}
			return nil
		})
		if err != nil {
			return err
		}
		if found == false {
			return chronograf.ErrUserNotFound
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	return &user, nil
}

// Get searches the UsersStore for user with name
func (s *UsersStore) Get(ctx context.Context, name string) (*chronograf.User, error) {
	u, err := s.get(ctx, name)
	if err != nil {
		return nil, err
	}
	return &chronograf.User{
		Name: u.Name,
	}, nil
}

// Add a new Users in the UsersStore.
func (s *UsersStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
	if err := s.client.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(UsersBucket)
		seq, err := b.NextSequence()
		if err != nil {
			return err
		}
		if v, err := internal.MarshalUser(u); err != nil {
			return err
		} else if err := b.Put(u64tob(seq), v); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return nil, err
	}

	return u, nil
}

// Delete the users from the UsersStore
func (s *UsersStore) Delete(ctx context.Context, user *chronograf.User) error {
	u, err := s.get(ctx, user.Name)
	if err != nil {
		return err
	}
	if err := s.client.db.Update(func(tx *bolt.Tx) error {
		if err := tx.Bucket(UsersBucket).Delete(u64tob(u.ID)); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	return nil
}

// Update a user
func (s *UsersStore) Update(ctx context.Context, usr *chronograf.User) error {
	u, err := s.get(ctx, usr.Name)
	if err != nil {
		return err
	}
	if err := s.client.db.Update(func(tx *bolt.Tx) error {
		u.Name = usr.Name
		if v, err := internal.MarshalUserPB(u); err != nil {
			return err
		} else if err := tx.Bucket(UsersBucket).Put(u64tob(u.ID), v); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	return nil
}

// All returns all users
func (s *UsersStore) All(ctx context.Context) ([]chronograf.User, error) {
	var users []chronograf.User
	if err := s.client.db.View(func(tx *bolt.Tx) error {
		if err := tx.Bucket(UsersBucket).ForEach(func(k, v []byte) error {
			var user chronograf.User
			if err := internal.UnmarshalUser(v, &user); err != nil {
				return err
			}
			users = append(users, user)
			return nil
		}); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return nil, err
	}

	return users, nil
}