package proxy

import (
	"context"
	"testing"
	"time"

	"github.com/standardbeagle/agnt/internal/protocol"
)

func TestNewTunnelManager(t *testing.T) {
	config := &protocol.TunnelConfig{
		Provider: "ngrok",
	}

	tm := NewTunnelManager(config, 8080)

	if tm == nil {
		t.Fatal("expected non-nil TunnelManager")
	}
	if tm.proxyPort != 8080 {
		t.Errorf("expected proxyPort 8080, got %d", tm.proxyPort)
	}
	if tm.config.Provider != "ngrok" {
		t.Errorf("expected provider ngrok, got %s", tm.config.Provider)
	}
	if tm.IsRunning() {
		t.Error("expected IsRunning to be false initially")
	}
	if tm.PublicURL() != "" {
		t.Errorf("expected empty PublicURL initially, got %s", tm.PublicURL())
	}
}

func TestTunnelManager_ExtractURL(t *testing.T) {
	tm := NewTunnelManager(&protocol.TunnelConfig{Provider: "ngrok"}, 8080)

	tests := []struct {
		name     string
		line     string
		expected string
	}{
		// ngrok patterns
		{
			name:     "ngrok forwarding",
			line:     "Forwarding https://abc123.ngrok.io -> http://localhost:8080",
			expected: "https://abc123.ngrok.io",
		},
		{
			name:     "ngrok-free app domain",
			line:     "https://abc123.ngrok-free.app",
			expected: "https://abc123.ngrok-free.app",
		},
		{
			name:     "ngrok url= format",
			line:     "url=https://def456.ngrok.io",
			expected: "https://def456.ngrok.io",
		},

		// cloudflared patterns
		{
			name:     "cloudflared trycloudflare",
			line:     "https://random-name-here.trycloudflare.com",
			expected: "https://random-name-here.trycloudflare.com",
		},
		{
			name:     "cloudflared with pipe separator",
			line:     "INF | https://random-name.trycloudflare.com |",
			expected: "https://random-name.trycloudflare.com",
		},

		// tailscale patterns
		{
			name:     "tailscale funnel",
			line:     "https://mybox.tail12345.ts.net",
			expected: "https://mybox.tail12345.ts.net",
		},
		{
			name:     "tailscale with port",
			line:     "Serving https://mybox.tailnet.ts.net:443",
			expected: "https://mybox.tailnet.ts.net:443",
		},

		// localtunnel patterns
		{
			name:     "localtunnel",
			line:     "your url is: https://warm-phones-lie.loca.lt",
			expected: "https://warm-phones-lie.loca.lt",
		},

		// Generic HTTPS
		{
			name:     "generic https",
			line:     "Tunnel ready at https://example.tunnel.dev",
			expected: "https://example.tunnel.dev",
		},

		// No match
		{
			name:     "no url",
			line:     "Starting tunnel...",
			expected: "",
		},
		{
			name:     "http not https",
			line:     "local server at http://localhost:8080",
			expected: "", // We mainly care about public HTTPS URLs
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tm.extractURL(tt.line)
			if result != tt.expected {
				t.Errorf("extractURL(%q) = %q, expected %q", tt.line, result, tt.expected)
			}
		})
	}
}

func TestTunnelManager_BuildCommand_Ngrok(t *testing.T) {
	tests := []struct {
		name         string
		config       *protocol.TunnelConfig
		port         int
		expectedCmd  string
		expectedArgs []string
		expectError  bool
	}{
		{
			name: "basic ngrok",
			config: &protocol.TunnelConfig{
				Provider: "ngrok",
			},
			port:         8080,
			expectedCmd:  "ngrok",
			expectedArgs: []string{"http", "8080"},
		},
		{
			name: "ngrok with auth token",
			config: &protocol.TunnelConfig{
				Provider:  "ngrok",
				AuthToken: "secret123",
			},
			port:         3000,
			expectedCmd:  "ngrok",
			expectedArgs: []string{"http", "3000", "--authtoken", "secret123"},
		},
		{
			name: "ngrok with region",
			config: &protocol.TunnelConfig{
				Provider: "ngrok",
				Region:   "eu",
			},
			port:         5000,
			expectedCmd:  "ngrok",
			expectedArgs: []string{"http", "5000", "--region", "eu"},
		},
		{
			name: "ngrok with extra args",
			config: &protocol.TunnelConfig{
				Provider: "ngrok",
				Args:     []string{"--hostname", "myapp.ngrok.io"},
			},
			port:         8080,
			expectedCmd:  "ngrok",
			expectedArgs: []string{"http", "8080", "--hostname", "myapp.ngrok.io"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tm := NewTunnelManager(tt.config, tt.port)
			cmd, args, err := tm.buildCommand()

			if tt.expectError {
				if err == nil {
					t.Error("expected error, got nil")
				}
				return
			}

			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			if cmd != tt.expectedCmd {
				t.Errorf("expected cmd %q, got %q", tt.expectedCmd, cmd)
			}

			if len(args) != len(tt.expectedArgs) {
				t.Fatalf("expected %d args, got %d: %v", len(tt.expectedArgs), len(args), args)
			}

			for i, expected := range tt.expectedArgs {
				if args[i] != expected {
					t.Errorf("args[%d] = %q, expected %q", i, args[i], expected)
				}
			}
		})
	}
}

func TestTunnelManager_BuildCommand_Cloudflared(t *testing.T) {
	config := &protocol.TunnelConfig{
		Provider: "cloudflared",
	}

	tm := NewTunnelManager(config, 9000)
	cmd, args, err := tm.buildCommand()

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if cmd != "cloudflared" {
		t.Errorf("expected cmd 'cloudflared', got %q", cmd)
	}

	expectedArgs := []string{"tunnel", "--url", "http://localhost:9000"}
	if len(args) != len(expectedArgs) {
		t.Fatalf("expected %d args, got %d: %v", len(expectedArgs), len(args), args)
	}

	for i, expected := range expectedArgs {
		if args[i] != expected {
			t.Errorf("args[%d] = %q, expected %q", i, args[i], expected)
		}
	}
}

func TestTunnelManager_BuildCommand_Tailscale(t *testing.T) {
	config := &protocol.TunnelConfig{
		Provider: "tailscale",
	}

	tm := NewTunnelManager(config, 4000)
	cmd, args, err := tm.buildCommand()

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if cmd != "tailscale" {
		t.Errorf("expected cmd 'tailscale', got %q", cmd)
	}

	expectedArgs := []string{"funnel", "4000"}
	if len(args) != len(expectedArgs) {
		t.Fatalf("expected %d args, got %d: %v", len(expectedArgs), len(args), args)
	}

	for i, expected := range expectedArgs {
		if args[i] != expected {
			t.Errorf("args[%d] = %q, expected %q", i, args[i], expected)
		}
	}
}

func TestTunnelManager_BuildCommand_Custom(t *testing.T) {
	tests := []struct {
		name         string
		config       *protocol.TunnelConfig
		port         int
		expectedCmd  string
		expectedArgs []string
		expectError  bool
	}{
		{
			name: "custom with port placeholder",
			config: &protocol.TunnelConfig{
				Provider: "custom",
				Command:  "my-tunnel",
				Args:     []string{"--port", "{{PORT}}", "--mode", "http"},
			},
			port:         7777,
			expectedCmd:  "my-tunnel",
			expectedArgs: []string{"--port", "7777", "--mode", "http"},
		},
		{
			name: "custom without command",
			config: &protocol.TunnelConfig{
				Provider: "custom",
			},
			port:        8080,
			expectError: true,
		},
		{
			name: "custom with port in command",
			config: &protocol.TunnelConfig{
				Provider: "custom",
				Command:  "expose-{{PORT}}",
				Args:     []string{},
			},
			port:         5555,
			expectedCmd:  "expose-5555",
			expectedArgs: []string{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tm := NewTunnelManager(tt.config, tt.port)
			cmd, args, err := tm.buildCommand()

			if tt.expectError {
				if err == nil {
					t.Error("expected error, got nil")
				}
				return
			}

			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			if cmd != tt.expectedCmd {
				t.Errorf("expected cmd %q, got %q", tt.expectedCmd, cmd)
			}

			if len(args) != len(tt.expectedArgs) {
				t.Fatalf("expected %d args, got %d: %v", len(tt.expectedArgs), len(args), args)
			}

			for i, expected := range tt.expectedArgs {
				if args[i] != expected {
					t.Errorf("args[%d] = %q, expected %q", i, args[i], expected)
				}
			}
		})
	}
}

func TestTunnelManager_BuildCommand_UnknownProvider(t *testing.T) {
	config := &protocol.TunnelConfig{
		Provider: "unknown-provider",
	}

	tm := NewTunnelManager(config, 8080)
	_, _, err := tm.buildCommand()

	if err == nil {
		t.Error("expected error for unknown provider")
	}
}

func TestTunnelManager_RecentOutput(t *testing.T) {
	tm := NewTunnelManager(&protocol.TunnelConfig{Provider: "ngrok"}, 8080)

	// Initially empty
	output := tm.RecentOutput()
	if len(output) != 0 {
		t.Errorf("expected empty output initially, got %d lines", len(output))
	}

	// Add some output manually (simulating monitorOutput behavior)
	tm.outputMu.Lock()
	tm.output = append(tm.output, "line 1", "line 2", "line 3")
	tm.outputMu.Unlock()

	output = tm.RecentOutput()
	if len(output) != 3 {
		t.Errorf("expected 3 lines, got %d", len(output))
	}

	// Verify it's a copy, not the original slice
	output[0] = "modified"
	original := tm.RecentOutput()
	if original[0] == "modified" {
		t.Error("RecentOutput should return a copy, not the original slice")
	}
}

func TestTunnelManager_WaitForURL_AlreadyAvailable(t *testing.T) {
	tm := NewTunnelManager(&protocol.TunnelConfig{Provider: "ngrok"}, 8080)
	tm.publicURL.Store("https://test.ngrok.io")
	tm.running.Store(true)

	url, err := tm.WaitForURL(100 * time.Millisecond)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if url != "https://test.ngrok.io" {
		t.Errorf("expected 'https://test.ngrok.io', got %q", url)
	}
}

func TestTunnelManager_WaitForURL_Timeout(t *testing.T) {
	tm := NewTunnelManager(&protocol.TunnelConfig{Provider: "ngrok"}, 8080)
	tm.running.Store(true)

	start := time.Now()
	_, err := tm.WaitForURL(100 * time.Millisecond)
	elapsed := time.Since(start)

	if err == nil {
		t.Error("expected timeout error")
	}
	if elapsed < 100*time.Millisecond {
		t.Errorf("expected at least 100ms elapsed, got %v", elapsed)
	}
}

func TestTunnelManager_WaitForURL_ProcessExited(t *testing.T) {
	tm := NewTunnelManager(&protocol.TunnelConfig{Provider: "ngrok"}, 8080)
	tm.running.Store(false) // Process not running

	_, err := tm.WaitForURL(1 * time.Second)
	if err == nil {
		t.Error("expected error when process not running")
	}
}

func TestTunnelManager_StartAlreadyRunning(t *testing.T) {
	tm := NewTunnelManager(&protocol.TunnelConfig{Provider: "ngrok"}, 8080)
	tm.running.Store(true)

	err := tm.Start(context.Background())
	if err == nil {
		t.Error("expected error when starting already running tunnel")
	}
}

func TestTunnelManager_StopNotRunning(t *testing.T) {
	tm := NewTunnelManager(&protocol.TunnelConfig{Provider: "ngrok"}, 8080)

	// Should not error when stopping a non-running tunnel
	err := tm.Stop()
	if err != nil {
		t.Errorf("unexpected error stopping non-running tunnel: %v", err)
	}
}

func TestTunnelManager_ProviderCaseInsensitive(t *testing.T) {
	providers := []string{"NGROK", "Ngrok", "CloudFlared", "CLOUDFLARE", "Tailscale", "TAILSCALE"}

	for _, provider := range providers {
		t.Run(provider, func(t *testing.T) {
			config := &protocol.TunnelConfig{
				Provider: provider,
			}
			tm := NewTunnelManager(config, 8080)
			_, _, err := tm.buildCommand()
			if err != nil {
				t.Errorf("provider %q should be recognized (case insensitive): %v", provider, err)
			}
		})
	}
}

// TestTunnelURLPatterns tests that the URL patterns compile and work correctly
func TestTunnelURLPatterns(t *testing.T) {
	// Verify all patterns compiled (would panic at init if not)
	if len(tunnelURLPatterns) == 0 {
		t.Error("expected at least one URL pattern")
	}

	// Test specific pattern matches
	testCases := []struct {
		input   string
		matches bool
	}{
		{"https://abc.ngrok.io", true},
		{"https://abc.ngrok-free.app", true},
		{"https://random.trycloudflare.com", true},
		{"https://box.tail123.ts.net", true},
		{"http://localhost:8080", false}, // We match http but prefer https in patterns
		{"not a url at all", false},
	}

	for _, tc := range testCases {
		matched := false
		for _, pattern := range tunnelURLPatterns {
			if pattern.MatchString(tc.input) {
				matched = true
				break
			}
		}
		if matched != tc.matches {
			t.Errorf("pattern match for %q: got %v, expected %v", tc.input, matched, tc.matches)
		}
	}
}
