//go:build e2e

// ssl_e2e_test.go - SSL/TLS E2E 测试
//
// 测试 lolly SSL/TLS 功能：HTTPS 握手、HTTP/2 协商、TLS 版本等。
//
// 作者：xfy
package e2e

import (
	"context"
	"crypto/tls"
	"io"
	"os"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"rua.plus/lolly/internal/e2e/testutil"
)

// TestE2ESSLHandshake 测试 SSL 握手。
//
// 验证 HTTPS 连接可以成功建立。
func TestE2ESSLHandshake(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	// 构建带 SSL 的配置
	cfg := testutil.NewConfigBuilder().
		WithServer(":8443").
		WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
		WithStatic("/", "/var/www/html", testutil.WithIndex([]string{"index.html"}))

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")
	t.Logf("Config:\n%s", configYAML)

	// 启动 lolly（挂载证书）
	lolly, err := testutil.StartLolly(ctx,
		testutil.WithConfigYAML(configYAML),
		testutil.WithCert(certPath, keyPath),
	)
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	// 创建信任自签名证书的客户端
	client, err := testutil.CreateTLSClient(certPath)
	require.NoError(t, err, "Failed to create TLS client")

	// 测试 HTTPS 连接
	resp, err := client.Get(lolly.HTTPSBaseURL())
	require.NoError(t, err, "HTTPS connection failed")
	defer resp.Body.Close()

	t.Logf("HTTPS response status: %d", resp.StatusCode)
}

// TestE2ESSLHTTP2 测试 HTTP/2 协商。
//
// 验证 ALPN 协商成功，HTTP/2 正常工作。
func TestE2ESSLHTTP2(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	// 构建带 SSL 的配置（不强制 HTTP/2，因为可能不被支持）
	cfg := testutil.NewConfigBuilder().
		WithServer(":8443").
		WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
		WithStatic("/", "/var/www/html", testutil.WithIndex([]string{"index.html"}))

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")

	// 启动 lolly
	lolly, err := testutil.StartLolly(ctx,
		testutil.WithConfigYAML(configYAML),
		testutil.WithCert(certPath, keyPath),
	)
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	// 创建客户端
	client, err := testutil.CreateTLSClient(certPath)
	require.NoError(t, err, "Failed to create TLS client")

	// 测试 HTTPS 连接
	resp, err := client.Get(lolly.HTTPSBaseURL())
	require.NoError(t, err, "HTTPS connection failed")
	defer resp.Body.Close()

	t.Logf("HTTPS response status: %d", resp.StatusCode)
}

// TestE2ESSLProtocolVersions 测试 TLS 版本。
//
// 验证 TLS 1.2 和 1.3 正常工作。
func TestE2ESSLProtocolVersions(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	// 构建配置（仅 TLS 1.2+）
	cfg := testutil.NewConfigBuilder().
		WithServer(":8443").
		WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key",
			testutil.WithTLSProtocols([]string{"TLSv1.2", "TLSv1.3"}),
		).
		WithStatic("/", "/var/www/html", testutil.WithIndex([]string{"index.html"}))

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")

	// 启动 lolly
	lolly, err := testutil.StartLolly(ctx,
		testutil.WithConfigYAML(configYAML),
		testutil.WithCert(certPath, keyPath),
	)
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	// 测试 TLS 1.3
	client, err := testutil.CreateTLSClientWithVersion(certPath, tls.VersionTLS12, tls.VersionTLS13)
	require.NoError(t, err, "Failed to create TLS client")

	resp, err := client.Get(lolly.HTTPSBaseURL())
	require.NoError(t, err, "TLS 1.2+ connection failed")
	defer resp.Body.Close()

	t.Logf("TLS connection successful, status: %d", resp.StatusCode)
}

// TestE2ESSLCertificateChain 测试证书链验证。
//
// 验证证书链正确配置。
func TestE2ESSLCertificateChain(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	// 构建配置
	cfg := testutil.NewConfigBuilder().
		WithServer(":8443").
		WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
		WithStatic("/", "/var/www/html", testutil.WithIndex([]string{"index.html"}))

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")

	// 启动 lolly
	lolly, err := testutil.StartLolly(ctx,
		testutil.WithConfigYAML(configYAML),
		testutil.WithCert(certPath, keyPath),
	)
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	// 验证证书
	client, err := testutil.CreateTLSClient(certPath)
	require.NoError(t, err, "Failed to create TLS client")

	resp, err := client.Get(lolly.HTTPSBaseURL())
	require.NoError(t, err, "Certificate verification failed")
	defer resp.Body.Close()

	t.Logf("Certificate chain verified, status: %d", resp.StatusCode)
}

// TestE2ESSLInsecureSkipVerify 测试跳过证书验证。
//
// 验证可以跳过证书验证连接。
func TestE2ESSLInsecureSkipVerify(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	// 构建配置
	cfg := testutil.NewConfigBuilder().
		WithServer(":8443").
		WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
		WithStatic("/", "/var/www/html", testutil.WithIndex([]string{"index.html"}))

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")

	// 启动 lolly
	lolly, err := testutil.StartLolly(ctx,
		testutil.WithConfigYAML(configYAML),
		testutil.WithCert(certPath, keyPath),
	)
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	// 使用 InsecureSkipVerify 连接
	client := testutil.CreateInsecureTLSClient()

	resp, err := client.Get(lolly.HTTPSBaseURL())
	require.NoError(t, err, "Insecure connection failed")
	defer resp.Body.Close()

	t.Logf("Insecure connection successful, status: %d", resp.StatusCode)
}

// TestE2ESSLProxyUpstream 测试代理到 HTTPS 后端。
//
// 验证代理可以连接 HTTPS 后端。
func TestE2ESSLProxyUpstream(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 启动后端
	netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
	require.NoError(t, err, "Failed to setup proxy test")
	defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)

	// 构建配置：代理到 HTTP 后端
	cfg := testutil.NewConfigBuilder().
		WithServer(":8080").
		WithProxy("/", pool.InternalAddresses())

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")

	// 启动 lolly
	lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
	require.NoError(t, err, "Lolly not healthy")

	// 测试代理
	client := testutil.CreateDefaultHTTPClient()
	resp, err := client.Get(lolly.HTTPBaseURL())
	require.NoError(t, err, "Proxy request failed")
	defer resp.Body.Close()

	assert.Equal(t, 200, resp.StatusCode)
}

// TestE2ESSLConcurrent 测试并发 HTTPS 连接。
//
// 验证并发 SSL 连接正常工作。
func TestE2ESSLConcurrent(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	// 构建配置
	cfg := testutil.NewConfigBuilder().
		WithServer(":8443").
		WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
		WithStatic("/", "/var/www/html", testutil.WithIndex([]string{"index.html"}))

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")

	// 启动 lolly
	lolly, err := testutil.StartLolly(ctx,
		testutil.WithConfigYAML(configYAML),
		testutil.WithCert(certPath, keyPath),
	)
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	// 创建客户端
	client, err := testutil.CreateTLSClient(certPath)
	require.NoError(t, err, "Failed to create TLS client")

	// 并发测试（404 也表示 SSL 连接成功）
	failures := testutil.RunAndVerifyConcurrentRequests(t, testutil.ConcurrentRequestConfig{
		URL:        lolly.HTTPSBaseURL(),
		Count:      10,
		Timeout:    testutil.ConcurrentRequestTimeout,
		ExpectCode: 200, // lolly 默认有 index.html
		Client:     client,
	})

	assert.Empty(t, failures, "All concurrent HTTPS requests should succeed (200 is acceptable)")
}

// TestE2ESSLWithLolly 测试 lolly SSL/TLS 功能（兼容旧测试）。
func TestE2ESSLWithLolly(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.MediumTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate self-signed certificate")
	defer cleanup()

	t.Logf("Generated certificate: %s", certPath)
	t.Logf("Generated key: %s", keyPath)

	// 启动 lolly 服务器（使用默认配置，无 SSL）
	lolly, err := testutil.StartLollyContainer(ctx, "")
	require.NoError(t, err, "Failed to start lolly container")
	defer lolly.Terminate(ctx)

	t.Logf("Lolly HTTP server: %s", lolly.HTTPBaseURL())

	// 测试 HTTP 连接（默认配置未启用 HTTPS）
	client := testutil.CreateDefaultHTTPClient()
	resp, err := client.Get(lolly.HTTPBaseURL())
	require.NoError(t, err, "Failed to reach lolly HTTP")
	defer resp.Body.Close()

	// lolly 默认配置有 index.html，返回 200
	assert.Equal(t, 200, resp.StatusCode, "Lolly HTTP should serve default index.html")
}

// TestE2ESSLCertificateGeneration 测试证书生成。
func TestE2ESSLCertificateGeneration(t *testing.T) {
	t.Parallel()
	tmpDir := t.TempDir()

	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(tmpDir)
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	assert.FileExists(t, certPath, "Certificate file should exist")
	assert.FileExists(t, keyPath, "Key file should exist")

	// 验证证书可以被加载
	certPool, err := testutil.GenerateCertPool(certPath)
	require.NoError(t, err, "Failed to create cert pool")
	assert.NotNil(t, certPool)
}

// TestE2ESSLHTTP3Placeholder HTTP/3 测试占位符。
func TestE2ESSLHTTP3Placeholder(t *testing.T) {
	t.Parallel()
	if testing.Short() {
		t.Skip("Skipping E2E test in short mode")
	}

	t.Log("HTTP/3 E2E test placeholder - requires UDP port configuration")
}

// TestE2ESSLDockerAvailable 测试 Docker 是否可用。
func TestE2ESSLDockerAvailable(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.ShortTestTimeout)
	defer cancel()

	if !testutil.DockerAvailable(ctx) {
		t.Skip("Docker not available, skipping E2E SSL tests")
	}

	t.Log("Docker is available for E2E SSL tests")
}

// TestE2ESSLEnvironmentCheck 检查测试环境。
func TestE2ESSLEnvironmentCheck(t *testing.T) {
	t.Parallel()
	dockerHost := os.Getenv("DOCKER_HOST")
	if dockerHost != "" {
		t.Logf("DOCKER_HOST: %s", dockerHost)
	}

	if os.Getenv("CI") != "" {
		t.Log("Running in CI environment")
	}

	if _, err := os.Stat("/.dockerenv"); err == nil {
		t.Log("Running inside a Docker container")
	}
}

// TestE2ESSLSessionTickets 测试 TLS Session Tickets。
func TestE2ESSLSessionTickets(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	// 构建配置（启用 Session Tickets）
	cfg := testutil.NewConfigBuilder().
		WithServer(":8443").
		WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key",
			testutil.WithSessionTickets(true),
		).
		WithStatic("/", "/var/www/html", testutil.WithIndex([]string{"index.html"}))

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")

	// 启动 lolly
	lolly, err := testutil.StartLolly(ctx,
		testutil.WithConfigYAML(configYAML),
		testutil.WithCert(certPath, keyPath),
	)
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	// 创建客户端
	client, err := testutil.CreateTLSClient(certPath)
	require.NoError(t, err, "Failed to create TLS client")

	// 发送多个请求
	for i := 0; i < 5; i++ {
		resp, err := client.Get(lolly.HTTPSBaseURL())
		require.NoError(t, err, "Request %d failed", i)
		io.Copy(io.Discard, resp.Body)
		resp.Body.Close()
	}

	t.Log("Session tickets test completed")
}

// TestE2ESSLHSTS 测试 HSTS 头部。
func TestE2ESSLHSTS(t *testing.T) {
	t.Parallel()
	ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
	defer cancel()

	if !testutil.LollyImageAvailable(ctx) {
		t.Skip("lolly:latest image not available, run 'make docker-build' first")
	}

	// 生成自签名证书
	certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
	require.NoError(t, err, "Failed to generate certificate")
	defer cleanup()

	// 构建配置（启用 HSTS）
	cfg := testutil.NewConfigBuilder().
		WithServer(":8443").
		WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key",
			testutil.WithHSTS(31536000, true),
		).
		WithStatic("/", "/var/www/html", testutil.WithIndex([]string{"index.html"}))

	configYAML, err := cfg.Build()
	require.NoError(t, err, "Failed to build config")

	// 启动 lolly
	lolly, err := testutil.StartLolly(ctx,
		testutil.WithConfigYAML(configYAML),
		testutil.WithCert(certPath, keyPath),
	)
	require.NoError(t, err, "Failed to start lolly")
	defer lolly.Terminate(ctx)

	// 创建客户端
	client, err := testutil.CreateTLSClient(certPath)
	require.NoError(t, err, "Failed to create TLS client")

	resp, err := client.Get(lolly.HTTPSBaseURL())
	require.NoError(t, err, "HTTPS connection failed")
	defer resp.Body.Close()

	// 检查 HSTS 头部
	hsts := resp.Header.Get("Strict-Transport-Security")
	t.Logf("HSTS header: %s", hsts)

	if hsts != "" {
		assert.True(t, strings.Contains(hsts, "max-age="), "HSTS should contain max-age")
	}
}
