package connectorfixture

import (
	"context"
	"encoding/binary"
	"net"
	"os"
	"os/exec"
	"strings"
	"testing"
	"time"
)

// Exercise OpenTiDB and the real MySQL driver, observing the wire rather than
// just asserting a configuration field. A TLS-only fixture must reject a
// plaintext server before sending its authentication response.
func TestOpenTiDBRequiresTLSBeforeAuthentication(t *testing.T) {
	if os.Getenv("CONNECTOR_FIXTURE_TLS_CHILD") == "1" {
		OpenTiDB(t)
		t.Fatal("plaintext-only server unexpectedly accepted")
	}
	for _, mode := range []string{"true", "1", "yes", "require", "verify-full"} {
		t.Run(mode, func(t *testing.T) { requireTLSBeforeAuthentication(t, mode) })
	}
}

func requireTLSBeforeAuthentication(t *testing.T, mode string) {
	t.Helper()
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer listener.Close()
	host, port, err := net.SplitHostPort(listener.Addr().String())
	if err != nil {
		t.Fatal(err)
	}
	t.Setenv("TIDB_HOST", host)
	t.Setenv("TIDB_PORT", port)
	t.Setenv("TIDB_USER", "fixture")
	t.Setenv("TIDB_PASSWORD", "fixture-password")
	t.Setenv("TIDB_TLS", mode)
	t.Setenv("NEXUSCRM_TIDB_REQUIRED", "1")
	t.Setenv("CONNECTOR_FIXTURE_TLS_CHILD", "1")
	type observation struct {
		authenticationSent bool
		err                error
	}
	observed := make(chan observation, 1)
	go func() {
		conn, err := listener.Accept()
		if err != nil {
			observed <- observation{err: err}
			return
		}
		defer conn.Close()
		_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
		// Protocol 4.1 + secure password exchange, deliberately without SSL.
		capabilities := uint32(1 | 0x200 | 0x8000 | 0x80000)
		greeting := append([]byte{10}, []byte("8.0.0-fixture\x00")...)
		greeting = binary.LittleEndian.AppendUint32(greeting, 1)
		greeting = append(greeting, []byte("12345678\x00")...)
		greeting = binary.LittleEndian.AppendUint16(greeting, uint16(capabilities))
		greeting = append(greeting, 33, 2, 0)
		greeting = binary.LittleEndian.AppendUint16(greeting, uint16(capabilities>>16))
		greeting = append(greeting, 21)
		greeting = append(greeting, make([]byte, 10)...)
		greeting = append(greeting, []byte("abcdefghijkl\x00mysql_native_password\x00")...)
		packet := append([]byte{byte(len(greeting)), 0, 0, 0}, greeting...)
		if _, err := conn.Write(packet); err != nil {
			observed <- observation{err: err}
			return
		}
		var response [1]byte
		n, _ := conn.Read(response[:])
		observed <- observation{authenticationSent: n != 0}
	}()
	ctx, cancel := context.WithTimeout(t.Context(), 6*time.Second)
	defer cancel()
	cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestOpenTiDBRequiresTLSBeforeAuthentication$", "-test.count=1")
	output, runErr := cmd.CombinedOutput()
	select {
	case got := <-observed:
		if got.err != nil {
			t.Fatal(got.err)
		}
		if got.authenticationSent {
			t.Fatal("TLS-required fixture sent authentication to a plaintext server")
		}
	case <-ctx.Done():
		t.Fatal("fixture never reached the protocol boundary")
	}
	if runErr == nil || !strings.Contains(string(output), "TLS requested but server does not support TLS") {
		t.Fatalf("expected explicit TLS refusal; exit=%v output=%s", runErr, output)
	}
}

func TestTiDBConfigTLSModes(t *testing.T) {
	for _, mode := range []string{"true", "1", "t", "yes", "require", "verify-full", " TrUe ", " VERIFY-FULL "} {
		t.Run("enabled_"+mode, func(t *testing.T) {
			t.Setenv("TIDB_TLS", mode)
			cfg, err := tiDBConfig()
			if err != nil {
				t.Fatal(err)
			}
			if cfg.TLSConfig != "true" || cfg.AllowFallbackToPlaintext {
				t.Fatalf("TLS mode %q did not require verified TLS", mode)
			}
		})
	}
	for _, mode := range []string{"", "0", "false", "f", " FALSE "} {
		t.Run("disabled_"+mode, func(t *testing.T) {
			t.Setenv("TIDB_TLS", mode)
			cfg, err := tiDBConfig()
			if err != nil {
				t.Fatal(err)
			}
			if cfg.TLSConfig != "" {
				t.Fatalf("TLS mode %q unexpectedly enabled TLS", mode)
			}
		})
	}
	t.Run("invalid", func(t *testing.T) {
		t.Setenv("TIDB_TLS", "tru")
		if _, err := tiDBConfig(); err == nil {
			t.Fatal("invalid TLS configuration accepted")
		}
	})
}
