package mysql

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/buserr"
	"github.com/1Panel-dev/1Panel/agent/global"
	"github.com/1Panel-dev/1Panel/agent/utils/mysql/client"
)

type MysqlClient interface {
	Create(info client.CreateInfo) error
	CreateUser(info client.CreateInfo, withDeleteDB bool) error
	CreateUserOnly(info client.UserInfo, password string, timeout uint) error
	CreateDatabase(info client.CreateInfo) error
	DeleteDatabase(info client.DeleteInfo) error
	DeleteUser(info client.UserInfo, version string, timeout uint) error
	UpdateUser(info client.UserUpdateInfo, timeout uint) error
	GrantUser(info client.GrantInfo, timeout uint) error
	RevokeGrant(info client.GrantInfo, timeout uint) error
	ListUsers(timeout uint) ([]client.UserInfo, error)
	ListGrants(timeout uint) ([]client.GrantInfo, error)
	Delete(info client.DeleteInfo) error

	ChangePassword(info client.PasswordChangeInfo) error
	ChangeAccess(info client.AccessChangeInfo) error

	Backup(info client.BackupInfo) error
	Recover(info client.RecoverInfo) error

	LoadFormatCollation(timeout uint) ([]dto.MysqlFormatCollationOption, error)
	SyncDB(version string) ([]client.SyncDBInfo, error)
	Close()
}

func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
	if conn.From == "local" {
		mysqlCli := conn.Type
		if mysqlCli == "mysql-cluster" {
			mysqlCli = "mysql"
		}
		connArgs := []string{"exec", conn.Address, mysqlCli, "-u" + conn.Username, "-p" + conn.Password, "-e"}
		return client.NewLocal(connArgs, conn.Type, conn.Address, conn.Password, conn.Database), nil
	}

	if strings.Contains(conn.Address, ":") {
		conn.Address = fmt.Sprintf("[%s]", conn.Address)
	}

	tlsItem, err := client.ConnWithSSL(conn.SSL, conn.SkipVerify, conn.ClientKey, conn.ClientCert, conn.RootCert)
	if err != nil {
		return nil, err
	}
	connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8%s", conn.Username, conn.Password, conn.Address, conn.Port, tlsItem)
	db, err := sql.Open("mysql", connArgs)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		global.LOG.Errorf("test mysql conn failed, err: %v", err)
		return nil, err
	}
	if errors.Is(ctx.Err(), context.DeadlineExceeded) {
		return nil, buserr.New("ErrExecTimeOut")
	}

	return client.NewRemote(client.Remote{
		Type:     conn.Type,
		Client:   db,
		Database: conn.Database,
		User:     conn.Username,
		Password: conn.Password,
		Address:  conn.Address,
		Port:     conn.Port,

		SSL:        conn.SSL,
		RootCert:   conn.RootCert,
		ClientKey:  conn.ClientKey,
		ClientCert: conn.ClientCert,
		SkipVerify: conn.SkipVerify,
	}), nil
}
