//go:build integration
// +build integration

package integration_test

import (
	"crypto/tls"
	"fmt"
	"testing"
	"time"

	"github.com/apache/cassandra-gocql-driver/v2"
	"go.uber.org/zap"

	"github.com/everything-on-mysql/cassandra-on-mysql/internal/config"
	"github.com/everything-on-mysql/cassandra-on-mysql/pkg/cql"
	"github.com/everything-on-mysql/cassandra-on-mysql/pkg/cql/executor"
	"github.com/everything-on-mysql/cassandra-on-mysql/pkg/mysqlbackend"
)

func TestAutoTLSConnection(t *testing.T) {
	requireMySQLConfig(t)

	host, port, stop, err := startProxyAutoTLS(mysqlCfg)
	if err != nil {
		t.Fatalf("auto-tls proxy start failed: %v", err)
	}
	defer stop()

	cluster := gocql.NewCluster(host)
	cluster.Port = port
	cluster.DisableInitialHostLookup = true
	cluster.Timeout = 5 * time.Second
	cluster.ConnectTimeout = 5 * time.Second
	cluster.Authenticator = gocql.PasswordAuthenticator{
		Username: mysqlCfg.user,
		Password: mysqlCfg.password,
	}
	cluster.SslOpts = &gocql.SslOptions{
		Config: &tls.Config{InsecureSkipVerify: true},
	}

	session, err := cluster.CreateSession()
	if err != nil {
		t.Fatalf("tls session create failed: %v", err)
	}
	defer session.Close()

	if err := session.Query("SELECT release_version FROM system.local").Consistency(gocql.One).Exec(); err != nil {
		t.Fatalf("tls query failed: %v", err)
	}
}

func startProxyAutoTLS(cfg mysqlConfig) (string, int, func(), error) {
	log := zap.NewNop()
	prevCfg := config.GetConfig()
	conf := config.DefaultConfig()
	conf.MySQL.Host = fmt.Sprintf("%s:%d", cfg.host, cfg.port)
	conf.MySQL.MaxOpenConns = 10
	conf.MySQL.MaxIdleConns = 5
	conf.MySQL.ConnectTimeout = 5 * time.Second
	conf.MySQL.ReadTimeout = 5 * time.Second
	conf.MySQL.WriteTimeout = 5 * time.Second
	conf.MySQL.ConnMaxLifetime = 2 * time.Minute
	conf.MySQL.ConnMaxIdleTime = 30 * time.Second
	conf.Server.Listeners = []config.ServerListenerConfig{
		{Name: "auto", Address: "127.0.0.1:0", TLS: config.ServerTLSConfig{Mode: "auto"}},
	}
	conf.Server.MaxConnections = 100
	config.SetConfig(conf)

	poolCfg := &mysqlbackend.PoolConfig{
		Host:            conf.MySQL.Host,
		MaxOpenConns:    conf.MySQL.MaxOpenConns,
		MaxIdleConns:    conf.MySQL.MaxIdleConns,
		ConnectTimeout:  conf.MySQL.ConnectTimeout,
		ReadTimeout:     conf.MySQL.ReadTimeout,
		WriteTimeout:    conf.MySQL.WriteTimeout,
		ConnMaxLifetime: conf.MySQL.ConnMaxLifetime,
		ConnMaxIdleTime: conf.MySQL.ConnMaxIdleTime,
		DSNParams:       conf.MySQL.DSNParams,
		PreparedStatements: mysqlbackend.PreparedStatementsConfig{
			Enabled:          conf.MySQL.Prepared.Enabled,
			MaxStatements:    conf.MySQL.Prepared.MaxStatements,
			IdleTTL:          conf.MySQL.Prepared.IdleTTL,
			CleanupInterval:  conf.MySQL.Prepared.CleanupInterval,
			PrewarmOnPrepare: conf.MySQL.Prepared.PrewarmOnPrepare,
		},
		TLS: mysqlbackend.TLSConfig{
			Mode:               conf.MySQL.TLS.Mode,
			CAFile:             conf.MySQL.TLS.CAFile,
			CertFile:           conf.MySQL.TLS.CertFile,
			KeyFile:            conf.MySQL.TLS.KeyFile,
			ServerName:         conf.MySQL.TLS.ServerName,
			InsecureSkipVerify: conf.MySQL.TLS.InsecureSkipVerify,
		},
	}

	poolMgr := mysqlbackend.NewPoolManager(poolCfg, log)
	queryExecutor := executor.NewExecutor(poolMgr, log)
	queryExecutor.Start()
	authenticator := cql.NewPasswordAuthenticator()
	handler := cql.NewMessageHandlerWithExecutor(log, authenticator, queryExecutor)

	server := cql.NewServer(conf.Server.Listeners[0].Address, handler, log, &cql.ServerOptions{
		ProtocolVersion: cql.ProtoVersion4,
		Listeners: []cql.ListenerConfig{
			{
				Name: conf.Server.Listeners[0].Name,
				Addr: conf.Server.Listeners[0].Address,
				TLS: &cql.TLSConfig{
					Mode:         conf.Server.Listeners[0].TLS.Mode,
					CertFile:     conf.Server.Listeners[0].TLS.CertFile,
					KeyFile:      conf.Server.Listeners[0].TLS.KeyFile,
					ClientCAFile: conf.Server.Listeners[0].TLS.ClientCAFile,
					ClientAuth:   conf.Server.Listeners[0].TLS.ClientAuth,
					MinVersion:   conf.Server.Listeners[0].TLS.MinVersion,
					MaxVersion:   conf.Server.Listeners[0].TLS.MaxVersion,
				},
			},
		},
	})
	if err := server.Start(); err != nil {
		_ = poolMgr.Close()
		config.SetConfig(prevCfg)
		return "", 0, nil, err
	}

	host, port := parseTCPAddr(server.Addr())
	stop := func() {
		_ = server.Stop()
		queryExecutor.Close()
		_ = poolMgr.Close()
		config.SetConfig(prevCfg)
	}
	return host, port, stop, nil
}
