package tools

import (
	"strings"
	"testing"

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

func TestValidateStringLen(t *testing.T) {
	assert.NoError(t, validateStringLen("field", "", 100))
	assert.NoError(t, validateStringLen("field", "short", 100))
	assert.NoError(t, validateStringLen("field", strings.Repeat("a", 100), 100))
	assert.Error(t, validateStringLen("field", strings.Repeat("a", 101), 100))

	err := validateStringLen("my_field", strings.Repeat("x", 200), 100)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "my_field")
	assert.Contains(t, err.Error(), "200 bytes")
	assert.Contains(t, err.Error(), "max 100")
}

func TestValidateArrayLen(t *testing.T) {
	assert.NoError(t, validateArrayLen("items", []string{}, 10))
	assert.NoError(t, validateArrayLen("items", []string{"a", "b"}, 10))
	assert.NoError(t, validateArrayLen("items", make([]string, 10), 10))
	assert.Error(t, validateArrayLen("items", make([]string, 11), 10))

	err := validateArrayLen("args", make([]int, 50), 20)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "args")
	assert.Contains(t, err.Error(), "50")
	assert.Contains(t, err.Error(), "max 20")
}

func TestValidatePort(t *testing.T) {
	assert.NoError(t, validatePort("port", 0))
	assert.NoError(t, validatePort("port", 80))
	assert.NoError(t, validatePort("port", 65535))
	assert.Error(t, validatePort("port", -1))
	assert.Error(t, validatePort("port", 65536))
}

func TestValidatePositiveLimit(t *testing.T) {
	assert.NoError(t, validatePositiveLimit("limit", 0, 100))
	assert.NoError(t, validatePositiveLimit("limit", 50, 100))
	assert.NoError(t, validatePositiveLimit("limit", 100, 100))
	assert.Error(t, validatePositiveLimit("limit", -1, 100))
	assert.Error(t, validatePositiveLimit("limit", 101, 100))
}

func TestValidateRunInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateRunInput(RunInput{
		Path:       "/home/user/project",
		ScriptName: "test",
		Command:    "go",
		Args:       []string{"test", "./..."},
		ID:         "test-run",
		Mode:       RunModeBackground,
	}))

	// Oversized path
	assert.Error(t, validateRunInput(RunInput{
		Path: strings.Repeat("/a", maxPathLength),
	}))

	// Too many args
	assert.Error(t, validateRunInput(RunInput{
		Args: make([]string, maxArrayElements+1),
	}))

	// Oversized individual arg
	assert.Error(t, validateRunInput(RunInput{
		Args: []string{strings.Repeat("x", maxPathLength+1)},
	}))
}

func TestValidateProcInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateProcInput(ProcInput{
		Action:    "status",
		ProcessID: "dev-server",
		Tail:      100,
		Port:      3000,
	}))

	// Oversized grep pattern
	assert.Error(t, validateProcInput(ProcInput{
		Grep: strings.Repeat("x", maxGrepPattern+1),
	}))

	// Invalid port
	assert.Error(t, validateProcInput(ProcInput{
		Port: 70000,
	}))

	// Negative tail
	assert.Error(t, validateProcInput(ProcInput{
		Tail: -1,
	}))
}

func TestValidateProxyInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateProxyInput(ProxyInput{
		Action:    "start",
		ID:        "dev",
		TargetURL: "http://localhost:3000",
		Port:      12345,
	}))

	// Oversized JS code
	assert.Error(t, validateProxyInput(ProxyInput{
		Code: strings.Repeat("x", maxCodeLength+1),
	}))

	// Too many chaos rules
	assert.Error(t, validateProxyInput(ProxyInput{
		ChaosRules: make([]ChaosRuleInput, maxChaosRules+1),
	}))

	// Invalid chaos rule within input
	assert.Error(t, validateProxyInput(ProxyInput{
		ChaosRule: &ChaosRuleInput{
			ID: strings.Repeat("x", maxIDLength+1),
		},
	}))

	// Oversized toast message
	assert.Error(t, validateProxyInput(ProxyInput{
		ToastMessage: strings.Repeat("x", maxMessageLength+1),
	}))
}

func TestValidateProxyLogInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateProxyLogInput(ProxyLogInput{
		ProxyID: "dev",
		Action:  "query",
		Types:   []string{"http", "error"},
		Limit:   100,
	}))

	// Oversized limit
	assert.Error(t, validateProxyLogInput(ProxyLogInput{
		Limit: maxLimit + 1,
	}))

	// Too many types
	assert.Error(t, validateProxyLogInput(ProxyLogInput{
		Types: make([]string, maxArrayElements+1),
	}))
}

func TestValidateGetErrorsInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateGetErrorsInput(GetErrorsInput{
		ProcessID: "dev",
		ProxyID:   "proxy-dev",
		Since:     "5m",
		Limit:     25,
	}))

	// Oversized process_id
	assert.Error(t, validateGetErrorsInput(GetErrorsInput{
		ProcessID: strings.Repeat("x", maxIDLength+1),
	}))

	// Negative limit
	assert.Error(t, validateGetErrorsInput(GetErrorsInput{
		Limit: -1,
	}))
}

func TestValidateSnapshotInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateSnapshotInput(SnapshotInput{
		Action:   "baseline",
		Name:     "before-refactor",
		Baseline: "v1",
	}))

	// Oversized name
	assert.Error(t, validateSnapshotInput(SnapshotInput{
		Name: strings.Repeat("x", maxIDLength+1),
	}))
}

func TestValidateBrowserInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateBrowserInput(BrowserInput{
		Action:  "start",
		ID:      "chrome-1",
		URL:     "http://localhost:3000",
		ProxyID: "dev",
	}))

	// Oversized URL
	assert.Error(t, validateBrowserInput(BrowserInput{
		URL: strings.Repeat("x", maxURLLength+1),
	}))
}

func TestValidateAutomationInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateAutomationInput(AutomationInput{
		Action:    "evaluate",
		SessionID: "sess-1",
		Script:    "document.title",
	}))

	// Oversized script
	assert.Error(t, validateAutomationInput(AutomationInput{
		Script: strings.Repeat("x", maxCodeLength+1),
	}))
}

func TestValidateTunnelInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateTunnelInput(TunnelInput{
		Action:    "start",
		ID:        "dev-tunnel",
		Provider:  "cloudflare",
		LocalPort: 3000,
	}))

	// Invalid port
	assert.Error(t, validateTunnelInput(TunnelInput{
		LocalPort: 70000,
	}))
}

func TestValidateDaemonInput(t *testing.T) {
	assert.NoError(t, validateDaemonInput(DaemonInput{Action: "status"}))
	assert.Error(t, validateDaemonInput(DaemonInput{
		Action: strings.Repeat("x", maxIDLength+1),
	}))
}

func TestValidateSessionInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateSessionInput(SessionInput{
		Action:  "send",
		Code:    "abc123",
		Message: "hello world",
	}))

	// Oversized message
	assert.Error(t, validateSessionInput(SessionInput{
		Message: strings.Repeat("x", maxMessageLength+1),
	}))
}

func TestValidateStoreInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateStoreInput(StoreInput{
		Action:   "set",
		Scope:    "global",
		Key:      "theme",
		Metadata: map[string]any{"tag": "v1"},
	}))

	// Oversized key
	assert.Error(t, validateStoreInput(StoreInput{
		Key: strings.Repeat("x", maxStringField+1),
	}))

	// Too many metadata entries
	bigMeta := make(map[string]any, maxArrayElements+1)
	for i := 0; i <= maxArrayElements; i++ {
		bigMeta[strings.Repeat("k", 5)+string(rune('a'+i%26))+string(rune('0'+i/26))] = i
	}
	assert.Error(t, validateStoreInput(StoreInput{
		Metadata: bigMeta,
	}))
}

func TestValidateResponsiveAuditInputSize(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateResponsiveAuditInputSize(ResponsiveAuditInput{
		ProxyID: "dev",
		Checks:  []string{"layout", "overflow"},
		Timeout: 10000,
	}))

	// Oversized timeout
	assert.Error(t, validateResponsiveAuditInputSize(ResponsiveAuditInput{
		Timeout: maxTimeoutMs + 1,
	}))

	// Too many viewports
	assert.Error(t, validateResponsiveAuditInputSize(ResponsiveAuditInput{
		Viewports: make([]ViewportInput, maxArrayElements+1),
	}))
}

func TestValidateCurrentPageInput(t *testing.T) {
	// Normal input passes
	assert.NoError(t, validateCurrentPageInput(CurrentPageInput{
		ProxyID:   "dev",
		Action:    "list",
		SessionID: "page-1",
		Limit:     5,
	}))

	// Oversized session_id
	assert.Error(t, validateCurrentPageInput(CurrentPageInput{
		SessionID: strings.Repeat("x", maxIDLength+1),
	}))

	// Invalid limit
	assert.Error(t, validateCurrentPageInput(CurrentPageInput{
		Limit: maxLimit + 1,
	}))
}

func TestValidationError(t *testing.T) {
	msg := validationError("run", assert.AnError)
	assert.Contains(t, msg, "invalid run input")
}

func TestValidateChaosRuleInput(t *testing.T) {
	// Normal rule passes
	assert.NoError(t, validateChaosRuleInput("rule", ChaosRuleInput{
		ID:         "latency-1",
		Name:       "Add latency",
		Type:       "latency",
		URLPattern: "/api/*",
		Methods:    []string{"GET", "POST"},
	}))

	// Oversized URL pattern
	assert.Error(t, validateChaosRuleInput("rule", ChaosRuleInput{
		URLPattern: strings.Repeat("x", maxURLLength+1),
	}))

	// Too many methods
	assert.Error(t, validateChaosRuleInput("rule", ChaosRuleInput{
		Methods: make([]string, maxArrayElements+1),
	}))
}
