package cmd

import (
	"path"
	"time"

	bolt "github.com/coreos/bbolt"
	log "github.com/go-pkgz/lgr"
	"github.com/pkg/errors"

	"github.com/go-pkgz/auth/avatar"
	"github.com/go-pkgz/mongo"
)

// AvatarCommand set of flags and command for avatar migration
// it converts all avatars from src.type to dst.type.
// Note: it is possible to run migration for the same types (src = dst) in order to resize all avatars.
type AvatarCommand struct {
	AvatarSrc AvatarGroup `group:"src" namespace:"src"`
	AvatarDst AvatarGroup `group:"dst" namespace:"dst"`
	Mongo     MongoGroup  `group:"mongo" namespace:"mongo" env-namespace:"MONGO"`

	migrator AvatarMigrator
	CommonOpts
}

// AvatarMigrator defines interface for migration
type AvatarMigrator interface {
	Migrate(avatar.Store, avatar.Store) (int, error)
}

type avatarMigrator struct{}

// Migrate from one avatar store to another. Can be used to convert between stores
func (a avatarMigrator) Migrate(dst, src avatar.Store) (int, error) {
	return avatar.Migrate(dst, src)
}

// Execute runs  with AvatarCommand parameters, entry point for "avatar" command
func (ac *AvatarCommand) Execute(args []string) error {
	log.Printf("[INFO] migrate avatars from %s to %s", ac.AvatarSrc.Type, ac.AvatarDst.Type)

	src, err := ac.makeAvatarStore(ac.AvatarSrc)
	if err != nil {
		return errors.Wrapf(err, "can't make avatart store for %s", ac.AvatarSrc.Type)
	}

	dst, err := ac.makeAvatarStore(ac.AvatarDst)
	if err != nil {
		return errors.Wrapf(err, "can't make avatart store for %s", ac.AvatarDst.Type)
	}

	if ac.migrator == nil {
		ac.migrator = avatarMigrator{}
	}

	count, err := ac.migrator.Migrate(dst, src)
	if err != nil {
		return err
	}

	if err = dst.Close(); err != nil {
		log.Printf("[WARN] failed to close dst store %s", ac.AvatarDst.Type)
	}
	if err = src.Close(); err != nil {
		log.Printf("[WARN] failed to close src store %s", ac.AvatarSrc.Type)
	}

	log.Printf("[INFO] completed, migrated avatars = %d", count)
	return nil
}

func (ac *AvatarCommand) makeAvatarStore(gr AvatarGroup) (avatar.Store, error) {
	log.Printf("[DEBUG] make avatar store, type=%s", gr.Type)
	switch gr.Type {
	case "fs":
		if err := makeDirs(gr.FS.Path); err != nil {
			return nil, err
		}
		return avatar.NewLocalFS(gr.FS.Path), nil
	case "mongo":
		mgServer, err := ac.makeMongo()
		if err != nil {
			return nil, errors.Wrap(err, "failed to create mongo server")
		}
		conn := mongo.NewConnection(mgServer, ac.Mongo.DB, "")
		return avatar.NewGridFS(conn), nil
	case "bolt":
		if err := makeDirs(path.Dir(gr.Bolt.File)); err != nil {
			return nil, err
		}
		return avatar.NewBoltDB(gr.Bolt.File, bolt.Options{})
	}
	return nil, errors.Errorf("unsupported avatar store type %s", gr.Type)
}

func (ac *AvatarCommand) makeMongo() (result *mongo.Server, err error) {
	if ac.Mongo.URL == "" {
		return nil, errors.New("no mongo URL provided")
	}
	return mongo.NewServerWithURL(ac.Mongo.URL, 10*time.Second)
}
