package cmd

import (
	"context"
	"fmt"
	"strings"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	_profile "github.com/usememos/memos/server/profile"
	"github.com/usememos/memos/store"
	"github.com/usememos/memos/store/db"
)

var (
	copydbCmdFlagFrom = "from"
	copydbCmd         = &cobra.Command{
		Use:   "copydb", // `copydb` is a shortened for 'copy database'
		Short: "Copy data between db drivers",
		Run: func(cmd *cobra.Command, _ []string) {
			s, err := cmd.Flags().GetString(copydbCmdFlagFrom)
			if err != nil {
				println("fail to get from driver DSN")
				println(err)
				return
			}
			ss := strings.Split(s, "://")
			if len(ss) != 2 {
				println("fail to parse from driver DSN, should be like 'sqlite://memos_prod.db' or 'mysql://user:pass@tcp(host)/memos'")
				return
			}

			fromProfile := &_profile.Profile{Driver: ss[0], DSN: ss[1]}

			err = copydb(fromProfile, profile)
			if err != nil {
				fmt.Printf("fail to copydb: %s\n", err)
				return
			}

			println("done")
		},
	}
)

func init() {
	copydbCmd.Flags().String(copydbCmdFlagFrom, "sqlite://memos_prod.db", "From driver DSN")

	rootCmd.AddCommand(copydbCmd)
}

func copydb(fromProfile, toProfile *_profile.Profile) error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	toDriver, err := db.NewDBDriver(toProfile)
	if err != nil {
		return errors.Wrap(err, "fail to create `to` driver")
	}

	if err := toDriver.Migrate(ctx); err != nil {
		return errors.Wrap(err, "fail to migrate db")
	}

	fromDriver, err := db.NewDBDriver(fromProfile)
	if err != nil {
		return errors.Wrap(err, "fail to create `from` driver")
	}

	// Register here if any table is added
	copyMap := map[string]func(context.Context, store.Driver, store.Driver) error{
		"activity":       copyActivity,
		"idp":            copyIdp,
		"memo":           copyMemo,
		"memo_organizer": copyMemoOrganizer,
		"memo_relation":  copyMemoRelation,
		"resource":       copyResource,
		"storage":        copyStorage,
		"system_setting": copySystemSettings,
		"tag":            copyTag,
		"user":           copyUser,
		"user_setting":   copyUserSettings,
	}

	toDb := toDriver.GetDB()
	for table := range copyMap {
		println("Checking " + table + "...")
		var cnt int
		err := toDb.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+table).Scan(&cnt)
		if err != nil {
			return errors.Wrapf(err, "fail to check '%s'", table)
		}
		if cnt > 0 {
			return errors.Errorf("table '%s' is not empty", table)
		}
	}

	for _, f := range copyMap {
		err = f(ctx, fromDriver, toDriver)
		if err != nil {
			return errors.Wrap(err, "fail to copy data")
		}
	}

	return nil
}

func copyActivity(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying Activity...")
	list, err := fromDriver.ListActivity(ctx, &store.FindActivity{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.CreateActivity(ctx, &store.Activity{
			ID:        item.ID,
			CreatorID: item.CreatorID,
			CreatedTs: item.CreatedTs,
			Level:     item.Level,
			Type:      item.Type,
			Payload:   item.Payload,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copyIdp(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying IdentityProvider...")
	list, err := fromDriver.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.CreateIdentityProvider(ctx, &store.IdentityProvider{
			ID:               item.ID,
			Name:             item.Name,
			Type:             item.Type,
			IdentifierFilter: item.IdentifierFilter,
			Config:           item.Config,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copyMemo(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying Memo...")
	list, err := fromDriver.ListMemos(ctx, &store.FindMemo{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.CreateMemo(ctx, &store.Memo{
			ID:         item.ID,
			CreatorID:  item.CreatorID,
			CreatedTs:  item.CreatedTs,
			UpdatedTs:  item.UpdatedTs,
			RowStatus:  item.RowStatus,
			Content:    item.Content,
			Visibility: item.Visibility,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copyMemoOrganizer(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying MemoOrganizer...")
	list, err := fromDriver.ListMemoOrganizer(ctx, &store.FindMemoOrganizer{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.UpsertMemoOrganizer(ctx, &store.MemoOrganizer{
			MemoID: item.MemoID,
			UserID: item.UserID,
			Pinned: item.Pinned,
		})
		if err != nil {
			return err
		}
	}
	println("\tDONE")
	return nil
}

func copyMemoRelation(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying MemoRelation...")
	list, err := fromDriver.ListMemoRelations(ctx, &store.FindMemoRelation{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.UpsertMemoRelation(ctx, &store.MemoRelation{
			MemoID:        item.MemoID,
			RelatedMemoID: item.RelatedMemoID,
			Type:          item.Type,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copyResource(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying Resource...")
	list, err := fromDriver.ListResources(ctx, &store.FindResource{GetBlob: true})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.CreateResource(ctx, &store.Resource{
			ID:           item.ID,
			CreatorID:    item.CreatorID,
			CreatedTs:    item.CreatedTs,
			UpdatedTs:    item.UpdatedTs,
			Filename:     item.Filename,
			Blob:         item.Blob,
			ExternalLink: item.ExternalLink,
			Type:         item.Type,
			Size:         item.Size,
			InternalPath: item.InternalPath,
			MemoID:       item.MemoID,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copyStorage(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying Storage...")
	list, err := fromDriver.ListStorages(ctx, &store.FindStorage{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.CreateStorage(ctx, &store.Storage{
			ID:     item.ID,
			Name:   item.Name,
			Type:   item.Type,
			Config: item.Config,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copySystemSettings(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying SystemSettings...")
	list, err := fromDriver.ListSystemSettings(ctx, &store.FindSystemSetting{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.UpsertSystemSetting(ctx, &store.SystemSetting{
			Name:        item.Name,
			Value:       item.Value,
			Description: item.Description,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copyTag(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying Tag...")
	list, err := fromDriver.ListTags(ctx, &store.FindTag{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.UpsertTag(ctx, &store.Tag{
			Name:      item.Name,
			CreatorID: item.CreatorID,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copyUser(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying User...")
	list, err := fromDriver.ListUsers(ctx, &store.FindUser{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.CreateUser(ctx, &store.User{
			ID:           item.ID,
			CreatedTs:    item.CreatedTs,
			UpdatedTs:    item.UpdatedTs,
			RowStatus:    item.RowStatus,
			Username:     item.Username,
			Role:         item.Role,
			Email:        item.Email,
			Nickname:     item.Nickname,
			PasswordHash: item.PasswordHash,
			AvatarURL:    item.AvatarURL,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}

func copyUserSettings(ctx context.Context, fromDriver, toDriver store.Driver) error {
	println("Copying UserSettings...")
	list, err := fromDriver.ListUserSettings(ctx, &store.FindUserSetting{})
	if err != nil {
		return err
	}

	fmt.Printf("\tTotal %d records\n", len(list))
	for _, item := range list {
		_, err := toDriver.UpsertUserSetting(ctx, &store.UserSetting{
			Key:    item.Key,
			Value:  item.Value,
			UserID: item.UserID,
		})
		if err != nil {
			return err
		}
	}

	println("\tDONE")
	return nil
}
