package testcases

import (
	"bytes"
	"context"
	"embed"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	pkgtestcases "github.com/vllm-project/semantic-router/e2e/pkg/testcases"
	"k8s.io/client-go/kubernetes"
)

//go:embed testdata/plugin_chain_cases.json
var pluginChainTestData embed.FS

func init() {
	pkgtestcases.Register("plugin-chain-execution", pkgtestcases.TestCase{
		Description: "Test plugin chain execution order and blocking behavior",
		Tags:        []string{"signal-decision", "plugin", "pii"},
		Fn:          testPluginChainExecution,
	})
}

// PluginChainCase represents a test case for plugin chain execution
type PluginChainCase struct {
	Description         string   `json:"description"`
	Query               string   `json:"query"`
	ExpectPIIBlock      bool     `json:"expect_pii_block"`
	ExpectCacheUsed     bool     `json:"expect_cache_used"`
	ExpectPromptApplied bool     `json:"expect_prompt_applied"`
	PIITypes            []string `json:"pii_types"` // Expected PII types detected
}

// loadPluginChainCases loads test cases from the embedded JSON file
func loadPluginChainCases() ([]PluginChainCase, error) {
	data, err := pluginChainTestData.ReadFile("testdata/plugin_chain_cases.json")
	if err != nil {
		return nil, fmt.Errorf("failed to read plugin chain cases: %w", err)
	}

	var cases []PluginChainCase
	if err := json.Unmarshal(data, &cases); err != nil {
		return nil, fmt.Errorf("failed to parse plugin chain cases: %w", err)
	}

	return cases, nil
}

// PluginChainResult tracks the result of a single plugin chain test
type PluginChainResult struct {
	Query               string
	PIIBlocked          bool
	PIIDetected         string
	CacheHit            bool
	PromptApplied       bool
	StatusCode          int
	ExpectPIIBlock      bool
	ExpectCacheUsed     bool
	ExpectPromptApplied bool
	Correct             bool
	Error               string
}

func testPluginChainExecution(ctx context.Context, client *kubernetes.Clientset, opts pkgtestcases.TestCaseOptions) error {
	if opts.Verbose {
		fmt.Println("[Test] Testing plugin chain execution order and blocking")
	}

	// Setup service connection and get local port
	localPort, stopPortForward, err := setupServiceConnection(ctx, client, opts)
	if err != nil {
		return err
	}
	defer stopPortForward()

	// Load test cases from JSON file
	testCases, err := loadPluginChainCases()
	if err != nil {
		return fmt.Errorf("failed to load test cases: %w", err)
	}

	if opts.Verbose {
		fmt.Printf("[Test] Loaded %d test cases from testdata/plugin_chain_cases.json\n", len(testCases))
	}

	// Run plugin chain tests
	var results []PluginChainResult
	totalTests := 0
	correctTests := 0

	for _, testCase := range testCases {
		totalTests++
		result := testSinglePluginChain(ctx, testCase, localPort, opts.Verbose)
		results = append(results, result)
		if result.Correct {
			correctTests++
		}

		// Small delay between tests to avoid overwhelming the system
		time.Sleep(500 * time.Millisecond)
	}

	// Calculate accuracy
	accuracy := float64(correctTests) / float64(totalTests) * 100

	// Set details for reporting
	if opts.SetDetails != nil {
		opts.SetDetails(map[string]interface{}{
			"total_tests":   totalTests,
			"correct_tests": correctTests,
			"accuracy_rate": fmt.Sprintf("%.2f%%", accuracy),
			"failed_tests":  totalTests - correctTests,
		})
	}

	// Print results
	printPluginChainResults(results, totalTests, correctTests, accuracy)

	if opts.Verbose {
		fmt.Printf("[Test] Plugin chain execution test completed: %d/%d correct (%.2f%% accuracy)\n",
			correctTests, totalTests, accuracy)
	}

	// Return error if accuracy is below threshold
	if correctTests == 0 {
		return fmt.Errorf("plugin chain execution test failed: 0%% accuracy (0/%d correct)", totalTests)
	}

	return nil
}

func testSinglePluginChain(ctx context.Context, testCase PluginChainCase, localPort string, verbose bool) PluginChainResult {
	result := PluginChainResult{
		Query:               testCase.Query,
		ExpectPIIBlock:      testCase.ExpectPIIBlock,
		ExpectCacheUsed:     testCase.ExpectCacheUsed,
		ExpectPromptApplied: testCase.ExpectPromptApplied,
	}

	// Create chat completion request
	requestBody := map[string]interface{}{
		"model": "MoM", // Use Mixture of Models to trigger decision engine
		"messages": []map[string]string{
			{"role": "user", "content": testCase.Query},
		},
	}

	jsonData, err := json.Marshal(requestBody)
	if err != nil {
		result.Error = fmt.Sprintf("failed to marshal request: %v", err)
		return result
	}

	// Send request
	url := fmt.Sprintf("http://localhost:%s/v1/chat/completions", localPort)
	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		result.Error = fmt.Sprintf("failed to create request: %v", err)
		return result
	}
	req.Header.Set("Content-Type", "application/json")
	// v0.4 demotes the intermediate decision/signal headers behind x-vsr-debug
	// (#2205); opt in so the routing assertions below can read them.
	req.Header.Set("x-vsr-debug", "true")

	httpClient := &http.Client{Timeout: 30 * time.Second}
	resp, err := httpClient.Do(req)
	if err != nil {
		result.Error = fmt.Sprintf("failed to send request: %v", err)
		return result
	}
	defer resp.Body.Close()

	result.StatusCode = resp.StatusCode

	// Extract plugin execution headers (signal-driven architecture)
	// When PII is detected and blocked, fast_response returns ImmediateResponse with:
	//   x-vsr-fast-response: true
	//   x-vsr-selected-decision: <decision-name>
	// Note: x-vsr-matched-pii is only set in the response header phase (non-blocking path),
	// it is NOT present in fast_response ImmediateResponse.
	fastResponseHeader := resp.Header.Get("x-vsr-fast-response")
	matchedPIIHeader := resp.Header.Get("x-vsr-matched-pii") // only in non-blocking path
	// Legacy header fallback for backward compatibility
	piiViolationHeader := resp.Header.Get("x-vsr-pii-violation")
	piiTypesHeader := resp.Header.Get("x-vsr-pii-types")

	if matchedPIIHeader != "" {
		result.PIIDetected = matchedPIIHeader // Non-blocking path header
	} else if piiTypesHeader != "" {
		result.PIIDetected = piiTypesHeader // Legacy fallback
	} else {
		result.PIIDetected = piiViolationHeader // Legacy boolean fallback
	}
	// PII is blocked when: fast_response was triggered (ImmediateResponse),
	// or legacy headers present
	result.PIIBlocked = fastResponseHeader == "true" ||
		resp.StatusCode == http.StatusForbidden ||
		piiViolationHeader == "true"

	// Check cache headers (x-vsr-cache-hit or similar)
	cacheHeader := resp.Header.Get("x-vsr-cache-hit")
	result.CacheHit = (cacheHeader == "true")

	// Check if system prompt was applied (x-vsr-selected-decision indicates routing happened)
	selectedDecision := resp.Header.Get("x-vsr-selected-decision")
	result.PromptApplied = (selectedDecision != "" && !result.PIIBlocked)

	// Determine correctness based on expectations
	piiCorrect := (result.PIIBlocked == testCase.ExpectPIIBlock)
	cacheCorrect := true // We don't strictly enforce cache behavior in this test
	promptCorrect := true

	// If PII was expected to block, other plugins shouldn't run
	if testCase.ExpectPIIBlock {
		promptCorrect = !result.PromptApplied // Prompt should NOT be applied if PII blocked
	}

	result.Correct = piiCorrect && cacheCorrect && promptCorrect

	logPluginChainResult(result, testCase, resp, verbose)

	return result
}

// logPluginChainResult prints the per-case outcome and, on an unexpected status,
// the response body — only when verbose. Extracted from testSinglePluginChain to
// keep it within the structure-lint function-size limit.
func logPluginChainResult(result PluginChainResult, testCase PluginChainCase, resp *http.Response, verbose bool) {
	if !verbose {
		return
	}

	if result.Correct {
		fmt.Printf("[Test] ✓ Plugin chain executed correctly\n")
		fmt.Printf("  Query: %s\n", truncateString(testCase.Query, 60))
		fmt.Printf("  PII Blocked: %v (expected: %v)\n", result.PIIBlocked, testCase.ExpectPIIBlock)
		if result.PIIDetected != "" {
			fmt.Printf("  PII Detected: %s\n", result.PIIDetected)
		}
	} else {
		fmt.Printf("[Test] ✗ Plugin chain execution failed\n")
		fmt.Printf("  Query: %s\n", testCase.Query)
		fmt.Printf("  Expected PII Block: %v, Actual: %v\n", testCase.ExpectPIIBlock, result.PIIBlocked)
		fmt.Printf("  PII Detected: %s\n", result.PIIDetected)
		fmt.Printf("  Status Code: %d\n", result.StatusCode)
		fmt.Printf("  Description: %s\n", testCase.Description)
	}

	// Surface the response body on an unexpected status.
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusForbidden {
		bodyBytes, _ := io.ReadAll(resp.Body)
		fmt.Printf("  Response: %s\n", string(bodyBytes))
	}
}

func printPluginChainResults(results []PluginChainResult, totalTests, correctTests int, accuracy float64) {
	separator := "================================================================================"
	fmt.Println("\n" + separator)
	fmt.Println("PLUGIN CHAIN EXECUTION TEST RESULTS")
	fmt.Println(separator)
	fmt.Printf("Total Tests: %d\n", totalTests)
	fmt.Printf("Correct Executions: %d\n", correctTests)
	fmt.Printf("Accuracy Rate: %.2f%%\n", accuracy)
	fmt.Println(separator)

	printPluginChainBreakdown(results)
	printPluginChainFailures(results)
	printPluginChainErrors(results)

	fmt.Println(separator + "\n")
}

// printPluginChainBreakdown prints accuracy split by PII-blocking vs clean query.
func printPluginChainBreakdown(results []PluginChainResult) {
	piiBlockTests, piiBlockCorrect := 0, 0
	cleanQueryTests, cleanQueryCorrect := 0, 0
	for _, result := range results {
		if result.ExpectPIIBlock {
			piiBlockTests++
			if result.Correct {
				piiBlockCorrect++
			}
		} else {
			cleanQueryTests++
			if result.Correct {
				cleanQueryCorrect++
			}
		}
	}

	fmt.Println("\nTest Breakdown:")
	if piiBlockTests > 0 {
		fmt.Printf("  - PII Blocking Tests: %d/%d (%.2f%%)\n",
			piiBlockCorrect, piiBlockTests, float64(piiBlockCorrect)/float64(piiBlockTests)*100)
	}
	if cleanQueryTests > 0 {
		fmt.Printf("  - Clean Query Tests:  %d/%d (%.2f%%)\n",
			cleanQueryCorrect, cleanQueryTests, float64(cleanQueryCorrect)/float64(cleanQueryTests)*100)
	}
}

// printPluginChainFailures prints cases with a wrong outcome (excluding request errors).
func printPluginChainFailures(results []PluginChainResult) {
	failedCount := 0
	for _, result := range results {
		if !result.Correct && result.Error == "" {
			failedCount++
		}
	}
	if failedCount == 0 {
		return
	}

	fmt.Println("\nFailed Plugin Chain Executions:")
	for _, result := range results {
		if !result.Correct && result.Error == "" {
			fmt.Printf("  - Query: %s\n", truncateString(result.Query, 70))
			fmt.Printf("    Expected PII Block: %v, Actual: %v\n", result.ExpectPIIBlock, result.PIIBlocked)
			fmt.Printf("    PII Detected: %s\n", result.PIIDetected)
			fmt.Printf("    Status Code: %d\n", result.StatusCode)
		}
	}
}

// printPluginChainErrors prints cases that failed with a request error.
func printPluginChainErrors(results []PluginChainResult) {
	errorCount := 0
	for _, result := range results {
		if result.Error != "" {
			errorCount++
		}
	}
	if errorCount == 0 {
		return
	}

	fmt.Println("\nErrors:")
	for _, result := range results {
		if result.Error != "" {
			fmt.Printf("  - Query: %s\n", truncateString(result.Query, 70))
			fmt.Printf("    Error: %s\n", result.Error)
		}
	}
}
