// Copyright (c) 2026 Alibaba Group and its affiliates

// Licensed 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 tdx

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

// ============================================================================
// Integration Tests - Real PCCS interaction
// ============================================================================

func TestGetCollateral(t *testing.T) {
	t.Run("GetCollateralFromPCCSForV4Quote", func(t *testing.T) {
		_, quote := loadTestQuote(t, "tdx_quote_4.dat")

		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")
		collateral, err := provider.GetCollateral(quote)

		if err != nil {
			t.Fatalf("Failed to get collateral: %v", err)
		}

		// Verify all required components are present
		if len(collateral.PckCrl) == 0 {
			t.Error("PCK CRL is empty")
		}
		if len(collateral.RootCaCrl) == 0 {
			t.Error("Root CA CRL is empty")
		}
		if len(collateral.TcbInfo) == 0 {
			t.Error("TCB Info is empty")
		}
		if len(collateral.QeIdentity) == 0 {
			t.Error("QE Identity is empty")
		}
		if len(collateral.PckCrlIssuerChain) == 0 {
			t.Error("PCK CRL Issuer Chain is empty")
		}
		if len(collateral.TcbInfoIssuerChain) == 0 {
			t.Error("TCB Info Issuer Chain is empty")
		}
		if len(collateral.QeIdentityIssuerChain) == 0 {
			t.Error("QE Identity Issuer Chain is empty")
		}
	})

	t.Run("GetCollateralFromPCCSForV5Quote", func(t *testing.T) {
		_, quote := loadTestQuote(t, "tdx_quote_5.dat")

		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")
		collateral, err := provider.GetCollateral(quote)

		if err != nil {
			t.Fatalf("Failed to get collateral: %v", err)
		}

		// Verify all required components are present
		if len(collateral.PckCrl) == 0 {
			t.Error("PCK CRL is empty")
		}
		if len(collateral.RootCaCrl) == 0 {
			t.Error("Root CA CRL is empty")
		}
		if len(collateral.TcbInfo) == 0 {
			t.Error("TCB Info is empty")
		}
		if len(collateral.QeIdentity) == 0 {
			t.Error("QE Identity is empty")
		}
		if len(collateral.PckCrlIssuerChain) == 0 {
			t.Error("PCK CRL Issuer Chain is empty")
		}
		if len(collateral.TcbInfoIssuerChain) == 0 {
			t.Error("TCB Info Issuer Chain is empty")
		}
		if len(collateral.QeIdentityIssuerChain) == 0 {
			t.Error("QE Identity Issuer Chain is empty")
		}
	})

	t.Run("CachingBehavior", func(t *testing.T) {
		_, quote := loadTestQuote(t, "tdx_quote_4.dat")

		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")

		// First fetch - should hit PCCS
		start1 := time.Now()
		_, err := provider.GetCollateral(quote)
		duration1 := time.Since(start1)
		if err != nil {
			t.Fatalf("First fetch failed: %v", err)
		}

		// Second fetch - should hit cache (much faster)
		start2 := time.Now()
		_, err = provider.GetCollateral(quote)
		duration2 := time.Since(start2)
		if err != nil {
			t.Fatalf("Second fetch failed: %v", err)
		}

		// Cache hit should be at least 10x faster
		if duration2 > duration1/10 {
			t.Logf("Warning: Second fetch (%v) not significantly faster than first (%v)", duration2, duration1)
			t.Logf("This might indicate cache is not working properly")
		} else {
			t.Logf("Cache performance: first=%v, second=%v (%.0fx faster)",
				duration1, duration2, float64(duration1)/float64(duration2))
		}
	})
}

// ============================================================================
// HTTP Retry Mechanism Tests
// ============================================================================

func TestHTTPRetryMechanism(t *testing.T) {
	t.Run("HTTPRetryWorksOnTransientFailures", func(t *testing.T) {
		attemptCount := 0
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			attemptCount++
			if attemptCount < 3 {
				// First 2 attempts fail with 503
				w.WriteHeader(http.StatusServiceUnavailable)
				return
			}
			// 3rd attempt succeeds
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("success"))
		}))
		defer server.Close()

		provider := NewCollateralProvider("")
		provider.SetRetryPolicy(3, 10*time.Millisecond) // Fast retry for testing

		resp, err := provider.httpGetWithRetry(server.URL)
		if err != nil {
			t.Fatalf("Expected success after retries, got error: %v", err)
		}
		defer resp.Body.Close()

		if attemptCount != 3 {
			t.Errorf("Expected 3 attempts, got %d", attemptCount)
		}

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Expected status 200, got %d", resp.StatusCode)
		}
	})

	t.Run("HTTPRetryExhaustsAndReturnsLastResponse", func(t *testing.T) {
		attemptCount := 0
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			attemptCount++
			// Always return 5xx error (retryable)
			w.WriteHeader(http.StatusInternalServerError)
		}))
		defer server.Close()

		provider := NewCollateralProvider("")
		provider.SetRetryPolicy(2, 10*time.Millisecond) // 2 retries for testing

		resp, err := provider.httpGetWithRetry(server.URL)
		if err != nil {
			t.Fatalf("Expected response after retries, got error: %v", err)
		}
		defer resp.Body.Close()

		// Should attempt 1 initial + 2 retries = 3 total
		if attemptCount != 3 {
			t.Errorf("Expected 3 attempts (1 initial + 2 retries), got %d", attemptCount)
		}

		// Should return last response with 500 status
		if resp.StatusCode != http.StatusInternalServerError {
			t.Errorf("Expected status 500, got %d", resp.StatusCode)
		}
	})

	t.Run("HTTPRetryWithZeroRetriesMeansNoRetry", func(t *testing.T) {
		attemptCount := 0
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			attemptCount++
			w.WriteHeader(http.StatusServiceUnavailable)
		}))
		defer server.Close()

		provider := NewCollateralProvider("")
		provider.SetRetryPolicy(0, 10*time.Millisecond) // No retries

		resp, err := provider.httpGetWithRetry(server.URL)
		if err != nil {
			t.Fatalf("Expected response, got error: %v", err)
		}
		defer resp.Body.Close()

		// Should only attempt once (no retries)
		if attemptCount != 1 {
			t.Errorf("Expected 1 attempt with no retries, got %d", attemptCount)
		}

		// Should return response with 503 status
		if resp.StatusCode != http.StatusServiceUnavailable {
			t.Errorf("Expected status 503, got %d", resp.StatusCode)
		}
	})

	t.Run("HTTPRetryDoesNotRetry4xxErrors", func(t *testing.T) {
		attemptCount := 0
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			attemptCount++
			// 4xx errors should not be retried
			w.WriteHeader(http.StatusBadRequest)
		}))
		defer server.Close()

		provider := NewCollateralProvider("")
		provider.SetRetryPolicy(3, 10*time.Millisecond)

		resp, err := provider.httpGetWithRetry(server.URL)
		if err != nil {
			t.Fatalf("Expected response, got error: %v", err)
		}
		defer resp.Body.Close()

		// Should only attempt once (4xx errors are not retried)
		if attemptCount != 1 {
			t.Errorf("Expected 1 attempt (no retry for 4xx), got %d", attemptCount)
		}

		if resp.StatusCode != http.StatusBadRequest {
			t.Errorf("Expected status 400, got %d", resp.StatusCode)
		}
	})
}

// ============================================================================
// LRU Cache Eviction Tests
// ============================================================================

func TestLRUCacheEviction(t *testing.T) {
	t.Run("LRUEvictsOldestEntryWhenCacheFull", func(t *testing.T) {
		_, quote1 := loadTestQuote(t, "tdx_quote_4.dat")
		_, quote2 := loadTestQuote(t, "tdx_quote_5.dat")

		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")
		provider.SetMaxCacheSize(1)         // Very small cache
		provider.SetCacheTTL(1 * time.Hour) // Long TTL

		// Fill cache with quote1's collateral
		_, err := provider.GetCollateral(quote1)
		if err != nil {
			t.Fatalf("Failed to get collateral for quote1: %v", err)
		}

		// Add quote2's collateral - should evict quote1's
		_, err = provider.GetCollateral(quote2)
		if err != nil {
			t.Fatalf("Failed to get collateral for quote2: %v", err)
		}

		// Try to get quote1's collateral again - should need to fetch from PCCS
		// (We measure time to verify it's fetching, not cached)
		start := time.Now()
		_, err = provider.GetCollateral(quote1)
		duration := time.Since(start)
		if err != nil {
			t.Fatalf("Failed to get collateral for quote1 after eviction: %v", err)
		}

		// If it was cached, it should be very fast (<10ms)
		// If it was fetched, it should take longer (>100ms typically)
		if duration < 10*time.Millisecond {
			t.Error("Quote1 collateral appears to still be cached, LRU eviction may not be working")
		} else {
			t.Logf("LRU eviction verified: quote1 re-fetch took %v (expected >10ms)", duration)
		}
	})

	t.Run("AccessingEntryRefreshesItsLRUPosition", func(t *testing.T) {
		_, quote1 := loadTestQuote(t, "tdx_quote_4.dat")
		_, quote2 := loadTestQuote(t, "tdx_quote_5.dat")

		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")
		provider.SetMaxCacheSize(2)
		provider.SetCacheTTL(1 * time.Hour)

		// Add quote1
		_, err := provider.GetCollateral(quote1)
		if err != nil {
			t.Fatalf("Failed to get collateral for quote1: %v", err)
		}
		time.Sleep(50 * time.Millisecond)

		// Add quote2
		_, err = provider.GetCollateral(quote2)
		if err != nil {
			t.Fatalf("Failed to get collateral for quote2: %v", err)
		}
		time.Sleep(50 * time.Millisecond)

		// Access quote1 again to refresh its LRU position
		start := time.Now()
		_, err = provider.GetCollateral(quote1)
		duration := time.Since(start)
		if err != nil {
			t.Fatalf("Failed to refresh quote1 collateral: %v", err)
		}

		// Should be cached (fast)
		if duration > 10*time.Millisecond {
			t.Errorf("Quote1 access should be cached (fast), took %v", duration)
		}

		t.Log("LRU refresh successful - quote1 was accessed and should remain in cache")
	})
}

// ============================================================================
// Error Handling Tests
// ============================================================================

func TestCollateralErrorHandling(t *testing.T) {
	t.Run("UnsupportedCertificationDataType", func(t *testing.T) {
		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")

		invalidQuote := &TdxEcdsaQuote{
			Header: QuoteHeader{
				TeeType: TEE_TYPE_TDX,
			},
			QeCertificationData: QeCertificationData{
				CertificationDataType: 99, // Invalid type
			},
		}

		_, err := provider.GetCollateral(invalidQuote)
		if err == nil {
			t.Fatal("Expected error for unsupported certification data type")
		}

		// Check error contains the key information about the failure path
		errMsg := err.Error()
		if !strings.Contains(errMsg, "failed to generate cache key") {
			t.Errorf("Expected error to contain 'failed to generate cache key', got: %s", errMsg)
		}
		if !strings.Contains(errMsg, "unsupported certification data type") {
			t.Errorf("Expected error to contain 'unsupported certification data type', got: %s", errMsg)
		}
	})

	t.Run("WrongCertificationDataStructureType", func(t *testing.T) {
		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")

		invalidQuote := &TdxEcdsaQuote{
			Header: QuoteHeader{
				TeeType: TEE_TYPE_TDX,
			},
			QeCertificationData: QeCertificationData{
				CertificationDataType: PCK_ID_QE_REPORT_CERTIFICATION_DATA,
				CertificationData:     []byte("wrong-type"), // Should be *QeReportCertificationData
			},
		}

		_, err := provider.GetCollateral(invalidQuote)
		if err == nil {
			t.Fatal("Expected error for wrong certification data structure")
		}

		errMsg := err.Error()
		if !strings.Contains(errMsg, "certification data is not QeReportCertificationData type") {
			t.Errorf("Expected error about wrong QeReportCertificationData type, got: %s", errMsg)
		}
		if !strings.Contains(errMsg, "got []uint8") {
			t.Errorf("Expected error to mention actual type '[]uint8', got: %s", errMsg)
		}
	})

	t.Run("InnerCertificationDataNotByteSlice", func(t *testing.T) {
		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")

		invalidQuote := &TdxEcdsaQuote{
			Header: QuoteHeader{
				TeeType: TEE_TYPE_TDX,
			},
			QeCertificationData: QeCertificationData{
				CertificationDataType: PCK_ID_QE_REPORT_CERTIFICATION_DATA,
				CertificationData: &QeReportCertificationData{
					QeCertificationData: QeCertificationData{
						CertificationDataType: PCK_ID_PCK_CERT_CHAIN,
						CertificationData:     "not-a-byte-slice", // Should be []byte
					},
				},
			},
		}

		_, err := provider.GetCollateral(invalidQuote)
		if err == nil {
			t.Fatal("Expected error for inner certification data not being byte slice")
		}

		errMsg := err.Error()
		if !strings.Contains(errMsg, "inner certification data is not []byte type") {
			t.Errorf("Expected error about inner data not being []byte, got: %s", errMsg)
		}
		if !strings.Contains(errMsg, "got string") {
			t.Errorf("Expected error to mention actual type 'string', got: %s", errMsg)
		}
	})

	t.Run("EmptyCertificationData", func(t *testing.T) {
		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")

		invalidQuote := &TdxEcdsaQuote{
			Header: QuoteHeader{
				TeeType: TEE_TYPE_TDX,
			},
			QeCertificationData: QeCertificationData{
				CertificationDataType: PCK_ID_QE_REPORT_CERTIFICATION_DATA,
				CertificationData: &QeReportCertificationData{
					QeCertificationData: QeCertificationData{
						CertificationDataType: PCK_ID_PCK_CERT_CHAIN,
						CertificationData:     []byte{}, // Empty
					},
				},
			},
		}

		_, err := provider.GetCollateral(invalidQuote)
		if err == nil {
			t.Fatal("Expected error for empty certification data")
		}

		errMsg := err.Error()
		if !strings.Contains(errMsg, "certification data is empty") {
			t.Errorf("Expected error 'certification data is empty', got: %s", errMsg)
		}
	})

	t.Run("InvalidCertificateDataFailsToParse", func(t *testing.T) {
		provider := NewCollateralProvider("https://sgx-dcap-server.cn-hangzhou.aliyuncs.com")

		invalidQuote := &TdxEcdsaQuote{
			Header: QuoteHeader{
				TeeType: TEE_TYPE_TDX,
			},
			QeCertificationData: QeCertificationData{
				CertificationDataType: PCK_ID_QE_REPORT_CERTIFICATION_DATA,
				CertificationData: &QeReportCertificationData{
					QeCertificationData: QeCertificationData{
						CertificationDataType: PCK_ID_PCK_CERT_CHAIN,
						CertificationData:     []byte("invalid-certificate-data-xxx"),
					},
				},
			},
		}

		_, err := provider.GetCollateral(invalidQuote)
		if err == nil {
			t.Fatal("Expected error for invalid certificate data")
		}

		errMsg := err.Error()
		if !strings.Contains(errMsg, "failed to parse certificate") {
			t.Errorf("Expected error about certificate parse failure, got: %s", errMsg)
		}
		if !strings.Contains(errMsg, "x509") {
			t.Errorf("Expected x509-related error, got: %s", errMsg)
		}
	})

	t.Run("NetworkErrorWithInvalidHost", func(t *testing.T) {
		_, quote := loadTestQuote(t, "tdx_quote_4.dat")

		// Use definitely invalid hostname
		provider := NewCollateralProvider("https://this-host-definitely-does-not-exist-12345.invalid")
		provider.SetRetryPolicy(0, 0) // No retries for faster test

		_, err := provider.GetCollateral(quote)
		if err == nil {
			t.Fatal("Expected network error for invalid hostname")
		}

		errMsg := err.Error()

		// Should indicate which collateral component failed
		hasComponentError := strings.Contains(errMsg, "failed to get PCK CRL") ||
			strings.Contains(errMsg, "failed to get root CA CRL") ||
			strings.Contains(errMsg, "failed to get TCB info") ||
			strings.Contains(errMsg, "failed to get QE identity")

		if !hasComponentError {
			t.Errorf("Expected error to indicate which collateral component failed, got: %s", errMsg)
		}

		// Should contain network error details
		hasNetworkError := strings.Contains(errMsg, "dial") ||
			strings.Contains(errMsg, "no such host") ||
			strings.Contains(errMsg, "lookup")

		if !hasNetworkError {
			t.Errorf("Expected network-related error (dial/lookup/no such host), got: %s", errMsg)
		}
	})
}
