/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package triple_protocol

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

import (
	"github.com/quic-go/quic-go/http3"

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

import (
	"dubbo.apache.org/dubbo-go/v3/global"
)

func TestParseAltSvcHeader(t *testing.T) {
	tests := []struct {
		name     string
		header   string
		expected []*AltSvcInfo
	}{
		{
			name:   "HTTP/3 only",
			header: `h3=":443"; ma=86400`,
			expected: []*AltSvcInfo{
				{
					Protocol: "h3",
					Host:     "",
					Port:     "443",
					Expires:  time.Now().Add(24 * time.Hour),
				},
			},
		},
		{
			name:   "HTTP/3 with host and port",
			header: `h3="example.com:443"; ma=3600`,
			expected: []*AltSvcInfo{
				{
					Protocol: "h3",
					Host:     "example.com",
					Port:     "443",
					Expires:  time.Now().Add(1 * time.Hour),
				},
			},
		},
		{
			name:   "Multiple protocols",
			header: `h3=":443"; ma=86400, h2=":443"; ma=86400`,
			expected: []*AltSvcInfo{
				{
					Protocol: "h3",
					Host:     "",
					Port:     "443",
					Expires:  time.Now().Add(24 * time.Hour),
				},
				{
					Protocol: "h2",
					Host:     "",
					Port:     "443",
					Expires:  time.Now().Add(24 * time.Hour),
				},
			},
		},
		{
			name:   "HTTP/3 with different port",
			header: `h3=":8443"; ma=7200`,
			expected: []*AltSvcInfo{
				{
					Protocol: "h3",
					Host:     "",
					Port:     "8443",
					Expires:  time.Now().Add(2 * time.Hour),
				},
			},
		},
		{
			name:     "Empty header",
			header:   "",
			expected: []*AltSvcInfo{},
		},
		{
			name:     "Invalid format",
			header:   `invalid-format`,
			expected: []*AltSvcInfo{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ParseAltSvcHeader(tt.header)
			assert.Len(t, result, len(tt.expected))

			for i, expected := range tt.expected {
				if i < len(result) {
					assert.Equal(t, expected.Protocol, result[i].Protocol)
					assert.Equal(t, expected.Host, result[i].Host)
					assert.Equal(t, expected.Port, result[i].Port)
					// Check that expires time is within a reasonable range
					assert.WithinDuration(t, expected.Expires, result[i].Expires, 5*time.Second)
				}
			}
		})
	}
}

func TestParseAuthority(t *testing.T) {
	tests := []struct {
		name         string
		authority    string
		expectedHost string
		expectedPort string
	}{
		{
			name:         "Port only",
			authority:    ":443",
			expectedHost: "",
			expectedPort: "443",
		},
		{
			name:         "Host and port",
			authority:    "example.com:443",
			expectedHost: "example.com",
			expectedPort: "443",
		},
		{
			name:         "Host only",
			authority:    "example.com",
			expectedHost: "example.com",
			expectedPort: "",
		},
		{
			name:         "Different port",
			authority:    ":8443",
			expectedHost: "",
			expectedPort: "8443",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			host, port := parseAuthority(tt.authority)
			assert.Equal(t, tt.expectedHost, host)
			assert.Equal(t, tt.expectedPort, port)
		})
	}
}

func TestUpdateAltSvcCache(t *testing.T) {
	cache := NewAltSvcCache()

	// Test HTTP/3 preference
	headers := http.Header{}
	headers.Set("Alt-Svc", `h3=":443"; ma=86400, h2=":443"; ma=86400`)

	cache.UpdateFromHeaders("example.com", headers)

	cached := cache.Get("example.com")
	assert.NotNil(t, cached)
	assert.Equal(t, "h3", cached.Protocol)
	assert.Equal(t, "443", cached.Port)
}

func TestGetAltSvcForHost(t *testing.T) {
	cache := NewAltSvcCache()

	// Test empty cache
	result := cache.Get("example.com")
	assert.Nil(t, result)

	// Test with cached data
	altSvc := &AltSvcInfo{
		Protocol: "h3",
		Host:     "",
		Port:     "443",
		Expires:  time.Now().Add(1 * time.Hour),
	}
	cache.Set("example.com", altSvc)

	result = cache.Get("example.com")
	assert.NotNil(t, result)
	assert.Equal(t, "h3", result.Protocol)
	assert.Equal(t, "443", result.Port)

	// Test expired cache
	expiredAltSvc := &AltSvcInfo{
		Protocol: "h3",
		Host:     "",
		Port:     "443",
		Expires:  time.Now().Add(-1 * time.Hour), // Expired
	}
	cache.Set("expired.com", expiredAltSvc)

	result = cache.Get("expired.com")
	assert.Nil(t, result)
}

func TestDualTransportWithAltSvc(t *testing.T) {
	// This test would need to be implemented in the client package
	// since dualTransport is defined there
	t.Skip("dualTransport test should be in client package")
}

func TestAltSvcHandler(t *testing.T) {
	// Create a mock HTTP/3 server
	http3Server := &http3.Server{
		Addr: ":0", // Use port 0 for testing
	}

	// Create a test handler
	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("test response"))
	})

	// Create AltSvcHandler wrapper
	altSvcHandler := NewAltSvcHandler(testHandler, http3Server, true) // Enable negotiation for testing

	// Test HTTP/2 request (should add Alt-Svc header)
	req := httptest.NewRequest("GET", "/test", nil)
	req.ProtoMajor = 2
	req.ProtoMinor = 0

	w := httptest.NewRecorder()
	altSvcHandler.ServeHTTP(w, req)

	// Check that Alt-Svc header is present (even if SetQUICHeaders fails in test environment)
	altSvcHeader := w.Header().Get("Alt-Svc")
	t.Logf("Alt-Svc header: %s", altSvcHeader)
	// Note: In test environment, SetQUICHeaders might fail, but the handler should still work
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "test response", w.Body.String())
}

func TestNewAltSvcHandler(t *testing.T) {
	// Create a mock HTTP/3 server
	http3Server := &http3.Server{
		Addr: ":0",
	}

	// Create a test handler
	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	// Create AltSvcHandler
	altSvcHandler := NewAltSvcHandler(testHandler, http3Server, true) // Enable negotiation for testing

	// Verify the handler is created correctly
	assert.NotNil(t, altSvcHandler)
	assert.NotNil(t, altSvcHandler.handler)
	assert.Equal(t, http3Server, altSvcHandler.http3Server)
}

func TestAltSvcHandlerNegotiation(t *testing.T) {
	// Create a mock HTTP/3 server
	http3Server := &http3.Server{
		Addr: ":0", // Use port 0 for testing
	}

	// Create a test handler
	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("test response"))
	})

	// Test with negotiation enabled
	altSvcHandlerEnabled := NewAltSvcHandler(testHandler, http3Server, true)

	req := httptest.NewRequest("GET", "/test", nil)
	req.ProtoMajor = 2
	req.ProtoMinor = 0

	w := httptest.NewRecorder()
	altSvcHandlerEnabled.ServeHTTP(w, req)

	// When negotiation is enabled, Alt-Svc header should be attempted to be set
	// (even if it fails in test environment)
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "test response", w.Body.String())

	// Test with negotiation disabled
	altSvcHandlerDisabled := NewAltSvcHandler(testHandler, http3Server, false)

	w2 := httptest.NewRecorder()
	altSvcHandlerDisabled.ServeHTTP(w2, req)

	// When negotiation is disabled, Alt-Svc header should not be set
	assert.Equal(t, http.StatusOK, w2.Code)
	assert.Equal(t, "test response", w2.Body.String())

	// Verify that the handler still works correctly regardless of negotiation setting
	assert.Equal(t, w.Header().Get("Alt-Svc"), w2.Header().Get("Alt-Svc"))
}

func TestNewServerWithTripleConf(t *testing.T) {
	// Test with negotiation enabled
	tripleConfEnabled := &global.TripleConfig{
		Http3: &global.Http3Config{
			Enable:      true,
			Negotiation: true,
		},
	}

	serverEnabled := NewServer("127.0.0.1:20001", tripleConfEnabled)
	assert.True(t, serverEnabled.tripleConfig.Http3.Negotiation)

	// Test with negotiation disabled
	tripleConfDisabled := &global.TripleConfig{
		Http3: &global.Http3Config{
			Enable:      true,
			Negotiation: false,
		},
	}

	serverDisabled := NewServer("127.0.0.1:20002", tripleConfDisabled)
	assert.False(t, serverDisabled.tripleConfig.Http3.Negotiation)

	// Test with nil tripleConf (should use default negotiation=true)
	serverDefault := NewServer("127.0.0.1:20003", nil)
	assert.Nil(t, serverDefault.tripleConfig)

	// Test with tripleConf but nil Http3 (should use default negotiation=true)
	tripleConfNilHttp3 := &global.TripleConfig{
		Http3: nil,
	}

	serverNilHttp3 := NewServer("127.0.0.1:20004", tripleConfNilHttp3)
	assert.NotNil(t, serverNilHttp3.tripleConfig)
	assert.Nil(t, serverNilHttp3.tripleConfig.Http3)
}
