// Copyright 2020 Lingfei Kong <colin404@foxmail.com>. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package set

import (
	"fmt"

	"github.com/jinzhu/gorm"

	// import mysql driver.
	_ "github.com/jinzhu/gorm/dialects/mysql"
	v1 "github.com/marmotedu/api/apiserver/v1"
	metav1 "github.com/marmotedu/component-base/pkg/meta/v1"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	cmdutil "github.com/marmotedu/iam/internal/iamctl/cmd/util"
	"github.com/marmotedu/iam/internal/iamctl/util/templates"
	"github.com/marmotedu/iam/pkg/cli/genericclioptions"
)

// DBOptions is an options struct to support 'db' sub command.
type DBOptions struct {
	host     string
	username string
	password string
	Database string

	drop  bool
	admin bool

	genericclioptions.IOStreams
}

var setExample = templates.Examples(`
		# Create new iam platform database and tables
		iamctl set db --mysql.host=127.0.0.1:3306 --mysql.username=iam --mysql.password=iamxxxx --mysql.database=iam

		# Create new iam platform database and tables with a administrator inserted
		iamctl set db --admin --mysql.host=127.0.0.1:3306 --mysql.username=iam --mysql.password=iamxxxx --mysql.database=iam

		# drop and create iam platform database and tables
		iamctl set db -d --mysql.host=127.0.0.1:3306 --mysql.username=iam --mysql.password=iamxxxx --mysql.database=iam`)

// NewDBOptions returns an initialized DBOptions instance.
func NewDBOptions(ioStreams genericclioptions.IOStreams) *DBOptions {
	return &DBOptions{
		host:     "127.0.0.1:3306",
		username: "root",
		password: "root",
		Database: "iam",

		drop:      false,
		admin:     false,
		IOStreams: ioStreams,
	}
}

// NewCmdDB returns new initialized instance of 'db' sub command.
func NewCmdDB(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewDBOptions(ioStreams)

	cmd := &cobra.Command{
		Use:                   "db",
		DisableFlagsInUseLine: true,
		Short:                 "Initialize the iam database",
		Long:                  "Initialize the iam database.",
		Example:               setExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete())
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.Run())
		},
		Aliases:    []string{},
		SuggestFor: []string{},
	}

	cmd.Flags().StringVar(&o.host, "host", o.host, "MySQL service host address.")
	cmd.Flags().StringVar(&o.username, "username", o.username, "username for access to mysql service.")
	cmd.Flags().StringVar(&o.password, "password", o.password,
		"password for access to mysql, should be used pair with password.")
	cmd.Flags().StringVar(&o.Database, "database", o.Database, "Database name for the server to use.")
	cmd.Flags().BoolVarP(&o.drop, "drop", "d", o.drop, "drop database if exists, pls double check the db name!")
	cmd.Flags().BoolVar(&o.admin, "admin", o.drop, "Insert a administrator user to the database.")

	_ = viper.BindPFlags(cmd.Flags())

	return cmd
}

// Complete completes all the required options.
func (o *DBOptions) Complete() error {
	// o.host = viper.GetString("host")
	// o.username = viper.GetString("username")
	// o.password = viper.GetString("password")
	// o.Database = viper.GetString("database")
	// o.drop = viper.GetBool("drop")
	// o.admin = viper.GetBool("admin")

	return nil
}

// Validate makes sure there is no discrepency in command options.
func (o *DBOptions) Validate() error {
	return nil
}

// Run executes a db sub command using the specified options.
func (o *DBOptions) Run() error {
	if err := o.ensureSchema(); err != nil {
		return err
	}

	dns := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s",
		o.username, o.password, o.host, o.Database, true, "Local")

	db, err := gorm.Open("mysql", dns)
	if err != nil {
		return err
	}
	defer db.Close()

	if db.HasTable(&v1.User{}) {
		db.AutoMigrate(&v1.User{})
	} else {
		db.Debug().CreateTable(&v1.User{})
	}

	if db.HasTable(&v1.Secret{}) {
		db.AutoMigrate(&v1.Secret{})
	} else {
		db.CreateTable(&v1.Secret{})
	}

	if db.HasTable(&v1.Policy{}) {
		db.AutoMigrate(&v1.Policy{})
	} else {
		db.CreateTable(&v1.Policy{})
	}
	fmt.Fprintf(o.Out, "update table success\n")

	if o.admin {
		if err := o.insertAdministrator(db); err != nil {
			return err
		}
	}

	return nil
}

func (o *DBOptions) ensureSchema() error {
	dns := fmt.Sprintf("%s:%s@tcp(%s)/?charset=utf8", o.username, o.password, o.host)

	db, err := gorm.Open("mysql", dns)
	if err != nil {
		return err
	}
	defer db.Close()

	if o.drop {
		dropSQL := fmt.Sprintf("DROP DATABASE IF EXISTS %s", o.Database)
		if err := db.Exec(dropSQL).Error; err != nil {
			return err
		}
		fmt.Fprintf(o.Out, "drop database %s success\n", o.Database)
	}

	createSQL := fmt.Sprintf("CREATE DATABASE if not exists %s CHARSET utf8 COLLATE utf8_general_ci", o.Database)
	if err := db.Exec(createSQL).Error; err != nil {
		return err
	}

	fmt.Fprintf(o.Out, "create database %s success\n", o.Database)

	return nil
}

func (o *DBOptions) insertAdministrator(db *gorm.DB) error {
	if !o.drop {
		return nil
	}

	// insert administrator user
	user := v1.User{
		ObjectMeta: metav1.ObjectMeta{
			Name: "admin",
		},
		Nickname: "admin",
		Password: "Admin@2020",
		Email:    "colin404@foxmail.com",
		Phone:    "1812884xxxx",
		IsAdmin:  1,
	}

	if err := db.Create(&user).Error; err != nil {
		return err
	}

	fmt.Fprintf(o.Out, "insert administrator success\n")

	return nil
}
