// Package route includes the test cases for testing different proxy routes.
package route

import (
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	"github.com/tidbcloud/clusterproxy/test/context"
	"github.com/tidbcloud/clusterproxy/util"
)

func forwardTiDB(ctx context.Context) chan struct{} {
	stop := make(chan struct{})
	ready := make(chan struct{})
	go ctx.Forward( // nolint:errcheck
		"db-tidb-0",
		fmt.Sprintf("tidb%d", ctx.ClusterId),
		fmt.Sprint(4000),
		stop,
		ready,
	)
	select {
	case <-ready:
	case <-time.After(time.Second * 10):
		Fail("Port forwarding timed out")
	}
	return stop
}

func connectTiDB(user string, password string) (*sql.DB, error) {
	conf := util.NewDefaultMysqlConfig()
	conf.User = user
	conf.Passwd = password
	conf.Addr = "localhost:4000"
	return sql.Open("mysql", conf.FormatDSN())
}

const changePasswordRoute = "/proxy/cluster/%d/change_password"

func doChangePassword(ctx context.Context, model passwordModel) ([]byte, error) {
	data, err := json.Marshal(model)
	Expect(err).To(Succeed())
	return ctx.ProxyGet(
		fmt.Sprintf(changePasswordRoute, ctx.ClusterId),
		nil,
		nil,
		data,
	)
}

type passwordModel struct {
	Password        string `json:"password"`
	CurrentPassword string `json:"currentPassword"`
	NewPassword     string `json:"newPassword"`
}

var _ = Describe("proxy routes test", func() {
	var (
		ctx *context.Context
		err error
	)
	It("should create context successfully", func() {
		ctx, err = context.New()
		Expect(err).To(Succeed())
		Expect(ctx).NotTo(BeNil())
	})

	Context("test change password", func() {
		const (
			newPassword = `\\\'''"""`
			password    = "password"
		)
		var encodedPassword = base64.URLEncoding.EncodeToString([]byte(password))
		var encodedNewPassword = base64.URLEncoding.EncodeToString([]byte(newPassword))
		It("should change the password", func() {
			body, err := doChangePassword(*ctx, passwordModel{
				CurrentPassword: encodedPassword,
				NewPassword:     encodedNewPassword,
			})
			Expect(string(body)).Should(BeEmpty(), "change password to escaped chars")
			Expect(err).To(Succeed(), "change password to escaped chars")

			stop := forwardTiDB(*ctx)
			defer func() { stop <- struct{}{} }()

			db, err := connectTiDB("root", password)
			Expect(err).To(Succeed())
			_, err = db.Query("use mysql;")
			Expect(err).ToNot(Succeed())

			db, err = connectTiDB("root", newPassword)
			Expect(err).To(Succeed())
			_, err = db.Query("use mysql;")
			Expect(err).To(Succeed())

			// Change the password back so it is consistent for other tests
			_, err = doChangePassword(*ctx, passwordModel{
				CurrentPassword: encodedPassword,
				NewPassword:     encodedPassword,
			})
			Expect(err).To(Succeed(), "restore original root password")
			Expect(body).Should(BeEmpty())

			db, err = connectTiDB("root", password)
			Expect(err).To(Succeed())
			_, err = db.Query("use mysql;")
			Expect(err).To(Succeed())
		})
	})
})
