package framework

import (
	"context"
	"fmt"
	"sync"
	"time"
)

type TestCase interface {
	Name() string
	Description() string
	Run(ctx *TestContext) *CaseResult
}

// TestContext test context containing common information for tests
type TestContext struct {
	ClusterID      string
	OrgID          string
	ProjectID      string
	RegionID       string
	Origin         string
	BizType        string
	AuthToken      string
	O11yGSURL      string // observability global server URL
	O11yGatewayURL string // observability gateway URL (for new path testing)
	ClinicURL      string // clinic server URL
	BaseURL        string // deprecated: use O11yGSURL or ClinicURL
	EnableNewPath  bool   // whether to test new API path
	Client         *HTTPClient
	Config         *Config
	ctx            context.Context

	// SharedData stores data that can be passed between test cases
	SharedData map[string]interface{}
	mu         sync.RWMutex
}

// NewTestContext creates a new test context
func NewTestContext(clusterID, authToken string, config *Config) *TestContext {
	client := NewHTTPClient(authToken, config.Timeout)

	// Set default request headers
	if config.RegionID != "" {
		client.SetHeader("X-Region-ID", config.RegionID)
	}
	if config.Origin != "" {
		client.SetHeader("Origin", config.Origin)
		client.SetHeader("Referer", config.Origin+"/")
	}
	// Set custom headers from config
	client.SetHeaders(config.Headers)

	return &TestContext{
		ClusterID:      clusterID,
		OrgID:          config.OrgID,
		ProjectID:      config.ProjectID,
		RegionID:       config.RegionID,
		Origin:         config.Origin,
		BizType:        config.BizType,
		AuthToken:      authToken,
		O11yGSURL:      config.O11yGSURL,
		O11yGatewayURL: config.O11yGatewayURL,
		ClinicURL:      config.ClinicURL,
		BaseURL:        config.O11yGSURL, // deprecated: for backward compatibility
		EnableNewPath:  config.EnableNewPath,
		Client:         client,
		Config:         config,
		ctx:            context.Background(),
		SharedData:     make(map[string]interface{}),
	}
}

// SetSharedData stores data for sharing between test cases
func (tc *TestContext) SetSharedData(key string, value interface{}) {
	tc.mu.Lock()
	defer tc.mu.Unlock()
	tc.SharedData[key] = value
}

// GetSharedData retrieves shared data by key
func (tc *TestContext) GetSharedData(key string) (interface{}, bool) {
	tc.mu.RLock()
	defer tc.mu.RUnlock()
	val, ok := tc.SharedData[key]
	return val, ok
}

// GetSharedString retrieves shared data as string
func (tc *TestContext) GetSharedString(key string) string {
	val, ok := tc.GetSharedData(key)
	if !ok {
		return ""
	}
	if s, ok := val.(string); ok {
		return s
	}
	return ""
}

// GetNewPathClient returns an HTTP client configured with newpath-specific headers
func (tc *TestContext) GetNewPathClient() *HTTPClient {
	client := NewHTTPClient(tc.AuthToken, tc.Config.Timeout)

	// Set standard headers
	if tc.Config.Origin != "" {
		client.SetHeader("Origin", tc.Config.Origin)
		client.SetHeader("Referer", tc.Config.Origin+"/")
	}

	// Set newpath-specific headers using existing config fields
	if tc.OrgID != "" {
		client.SetHeader("X-Org-Id", tc.OrgID)
	}
	if tc.ClusterID != "" {
		client.SetHeader("X-Application-Id", tc.ClusterID)
	}
	if tc.ProjectID != "" {
		client.SetHeader("X-Project-Id", tc.ProjectID)
	}
	if tc.RegionID != "" {
		client.SetHeader("X-Region-Id", tc.RegionID)
	}
	if tc.BizType != "" {
		client.SetHeader("X-Biz-Type", tc.BizType)
	}

	// Set custom headers from config
	client.SetHeaders(tc.Config.Headers)

	return client
}

func (tc *TestContext) Context() context.Context {
	return tc.ctx
}

func (tc *TestContext) WithContext(ctx context.Context) *TestContext {
	tc.ctx = ctx
	return tc
}

type TestFramework struct {
	testContext *TestContext
	registry    *Registry
	config      *Config
}

func NewTestFramework(clusterID, authToken string, config *Config) *TestFramework {
	if config == nil {
		config = DefaultConfig()
	}
	return &TestFramework{
		testContext: NewTestContext(clusterID, authToken, config),
		registry:    NewRegistry(),
		config:      config,
	}
}

func (tf *TestFramework) Register(cases ...TestCase) {
	for _, c := range cases {
		tf.registry.Register(c)
	}
}

// GetConfig returns the test framework configuration
func (tf *TestFramework) GetConfig() *Config {
	return tf.config
}

func (tf *TestFramework) Run() *TestReport {
	report := NewTestReport()
	report.StartTime = time.Now()
	report.ClusterID = tf.testContext.ClusterID
	report.GSURL = tf.testContext.O11yGSURL
	report.GatewayURL = tf.testContext.O11yGatewayURL
	report.ClinicURL = tf.testContext.ClinicURL
	report.BizType = tf.config.BizType
	report.RegionID = tf.config.RegionID

	cases := tf.registry.GetAll()
	report.TotalCases = len(cases)

	fmt.Printf("🚀 Starting WebConsole E2E Test\n")
	fmt.Printf("   Cluster ID: %s\n", tf.testContext.ClusterID)
	fmt.Printf("   O11y GS URL: %s\n", tf.testContext.O11yGSURL)
	fmt.Printf("   Clinic URL: %s\n", tf.testContext.ClinicURL)
	fmt.Printf("   Total Cases: %d\n\n", len(cases))

	for i, tc := range cases {
		fmt.Printf("📋 [%d/%d] Running: %s\n", i+1, len(cases), tc.Name())

		result := tf.runCaseWithRetry(tc)
		report.AddResult(result)

		if result.Success {
			fmt.Printf("   ✅ PASSED (%.2fs)\n\n", result.Duration.Seconds())
		} else {
			fmt.Printf("   ❌ FAILED (%.2fs)\n", result.Duration.Seconds())
			for _, err := range result.Errors {
				fmt.Printf("      Error: %s\n", err)
			}
			fmt.Println()
		}
	}

	report.EndTime = time.Now()
	report.Duration = report.EndTime.Sub(report.StartTime)

	tf.printSummary(report)

	return report
}

func (tf *TestFramework) runCaseWithRetry(tc TestCase) *CaseResult {
	var result *CaseResult

	maxRetries := tf.config.RetryCount
	for attempt := 0; attempt <= maxRetries; attempt++ {
		if attempt > 0 {
			fmt.Printf("   🔄 Retry attempt %d/%d\n", attempt, maxRetries)
			time.Sleep(tf.config.RetryDelay)
		}

		startTime := time.Now()
		result = tc.Run(tf.testContext)
		result.Duration = time.Since(startTime)
		result.CaseName = tc.Name()
		result.Description = tc.Description()

		if result.Success {
			break
		}
	}

	return result
}

func (tf *TestFramework) printSummary(report *TestReport) {
	fmt.Println("═══════════════════════════════════════════")
	fmt.Println("📊 Test Summary")
	fmt.Println("═══════════════════════════════════════════")
	fmt.Printf("   Total:   %d\n", report.TotalCases)
	fmt.Printf("   Passed:  %d\n", report.PassedCases)
	fmt.Printf("   Failed:  %d\n", report.FailedCases)
	fmt.Printf("   Duration: %.2fs\n", report.Duration.Seconds())
	fmt.Println("═══════════════════════════════════════════")

	if report.FailedCases > 0 {
		fmt.Println("\n❌ Failed Cases:")
		for _, r := range report.Results {
			if !r.Success {
				fmt.Printf("   - %s\n", r.CaseName)
				for _, err := range r.Errors {
					fmt.Printf("     • %s\n", err)
				}
			}
		}
	}
}

type Registry struct {
	cases []TestCase
	mu    sync.RWMutex
}

func NewRegistry() *Registry {
	return &Registry{
		cases: make([]TestCase, 0),
	}
}

func (r *Registry) Register(tc TestCase) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.cases = append(r.cases, tc)
}

func (r *Registry) GetAll() []TestCase {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.cases
}

func (r *Registry) Count() int {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return len(r.cases)
}
