package service

import (
	"context"
	"database/sql"
	"fmt"
	"os"
	"time"

	"github.com/go-sql-driver/mysql"
	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/tidbcloud/cluster-service-ng/internal/cluster-agent/internal/sqlexec"
)

// resetPasswordDirectFunc is a package-level variable for test injection of resetPasswordViaDirect.
var resetPasswordDirectFunc = func(s *AgentService, ctx context.Context, clusterID, username, password string, useTiDBWorker bool) error {
	return s.resetPasswordViaDirect(ctx, clusterID, username, password, useTiDBWorker)
}

// resetPasswordViaDirect resets a user's password by connecting directly to normal TiDB
// via MySQL protocol as the target user with an empty password, then executing ALTER USER.
// This is intended as a V0 fallback for first-time password initialization when the user
// has never had a password set.
func (s *AgentService) resetPasswordViaDirect(ctx context.Context, clusterID, username, password string, useTiDBWorker bool) error {
	logger := tilog.WithContextS(ctx).With("cluster_id", clusterID, "target_user", username)

	namespace := os.Getenv("NAMESPACE")
	if namespace == "" {
		return fmt.Errorf("NAMESPACE is empty")
	}

	// Resolve TiDB address based on whether this targets a worker instance.
	var addr string
	var err error
	if useTiDBWorker {
		addr, err = resolveWorkerTiDBAddr(ctx, s.dataplaneCli)
	} else {
		addr, err = resolveTiDBAddr(ctx, s.dataplaneCli, clusterID)
	}
	if err != nil {
		return fmt.Errorf("resolve TiDB address: %w", err)
	}

	// Load TLS config for the MySQL connection.
	tlsConfig, err := GetTLSConfig(ctx, s.dataplaneCli, namespace, tlsClientSecret)
	if err != nil {
		return fmt.Errorf("get TLS config: %w", err)
	}

	// Build MySQL config: connect as the target user with an empty password (first-time init).
	// Set TLS directly on the config to avoid global TLS registry race conditions.
	cfg := mysql.NewConfig()
	cfg.User = username
	cfg.Passwd = ""
	cfg.Net = "tcp"
	cfg.Addr = addr
	cfg.TLS = tlsConfig
	cfg.Timeout = 5 * time.Second
	cfg.ReadTimeout = 30 * time.Second
	cfg.WriteTimeout = 30 * time.Second
	cfg.AllowNativePasswords = true

	connector, err := mysql.NewConnector(cfg)
	if err != nil {
		return fmt.Errorf("create MySQL connector: %s", sqlexec.SanitizeSQLError(err))
	}
	db := sql.OpenDB(connector)
	defer func() { _ = db.Close() }()

	db.SetMaxOpenConns(1)
	db.SetMaxIdleConns(1)

	if err := db.PingContext(ctx); err != nil {
		return fmt.Errorf("ping TiDB: %s", sqlexec.SanitizeSQLError(err))
	}

	// Build and execute the ALTER USER command.
	cmd := &sqlexec.ResetPasswordCommand{
		Username: username,
		Password: sqlexec.NewSecretString(password),
	}
	if err := cmd.Validate(); err != nil {
		return fmt.Errorf("validate reset password command: %w", err)
	}
	stmt, err := cmd.Build()
	if err != nil {
		return fmt.Errorf("build reset password command: %w", err)
	}

	if _, err := db.ExecContext(ctx, stmt.SQL, stmt.Args...); err != nil {
		return fmt.Errorf("exec ALTER USER: %s", sqlexec.SanitizeSQLError(err))
	}

	logger.Infof("password reset via direct MySQL connection succeeded")
	return nil
}
