package interaction

import (
	"context"
	"errors"
	"io"
	"net"
	"testing"
	"time"
	"tunnel_pls/internal/types"

	"github.com/charmbracelet/bubbles/key"
	"github.com/charmbracelet/bubbles/list"
	"github.com/charmbracelet/bubbles/textinput"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"golang.org/x/crypto/ssh"
)

type MockRandom struct {
	mock.Mock
}

func (m *MockRandom) String(length int) (string, error) {
	args := m.Called(length)
	return args.String(0), args.Error(1)
}

type MockConfig struct {
	mock.Mock
}

func (m *MockConfig) Domain() string            { return m.Called().String(0) }
func (m *MockConfig) SSHPort() string           { return m.Called().String(0) }
func (m *MockConfig) HTTPPort() string          { return m.Called().String(0) }
func (m *MockConfig) HTTPSPort() string         { return m.Called().String(0) }
func (m *MockConfig) TLSEnabled() bool          { return m.Called().Bool(0) }
func (m *MockConfig) TLSRedirect() bool         { return m.Called().Bool(0) }
func (m *MockConfig) ACMEEmail() string         { return m.Called().String(0) }
func (m *MockConfig) CFAPIToken() string        { return m.Called().String(0) }
func (m *MockConfig) ACMEStaging() bool         { return m.Called().Bool(0) }
func (m *MockConfig) AllowedPortsStart() uint16 { return uint16(m.Called().Int(0)) }
func (m *MockConfig) AllowedPortsEnd() uint16   { return uint16(m.Called().Int(0)) }
func (m *MockConfig) BufferSize() int           { return m.Called().Int(0) }
func (m *MockConfig) HeaderSize() int           { return m.Called().Int(0) }
func (m *MockConfig) PprofEnabled() bool        { return m.Called().Bool(0) }
func (m *MockConfig) PprofPort() string         { return m.Called().String(0) }
func (m *MockConfig) Mode() types.ServerMode    { return m.Called().Get(0).(types.ServerMode) }
func (m *MockConfig) GRPCAddress() string       { return m.Called().String(0) }
func (m *MockConfig) GRPCPort() string          { return m.Called().String(0) }
func (m *MockConfig) NodeToken() string         { return m.Called().String(0) }
func (m *MockConfig) TLSStoragePath() string    { return m.Called().String(0) }
func (m *MockConfig) KeyLoc() string            { return m.Called().String(0) }

type MockSlug struct {
	mock.Mock
}

func (ms *MockSlug) Set(slug string) { ms.Called(slug) }
func (ms *MockSlug) String() string  { return ms.Called().String(0) }

type MockForwarder struct {
	mock.Mock
}

func (m *MockForwarder) CreateForwardedTCPIPPayload(origin net.Addr) []byte {
	args := m.Called(origin)
	return args.Get(0).([]byte)
}

func (m *MockForwarder) HandleConnection(dst io.ReadWriter, src ssh.Channel) {
	m.Called(dst, src)
}

func (m *MockForwarder) Close() error {
	args := m.Called()
	return args.Error(0)
}

func (m *MockForwarder) TunnelType() types.TunnelType {
	args := m.Called()
	return args.Get(0).(types.TunnelType)
}

func (m *MockForwarder) ForwardedPort() uint16 {
	args := m.Called()
	return args.Get(0).(uint16)
}

func (m *MockForwarder) SetType(tunnelType types.TunnelType) {
	m.Called(tunnelType)
}

func (m *MockForwarder) SetForwardedPort(port uint16) {
	m.Called(port)
}

func (m *MockForwarder) SetListener(listener net.Listener) {
	m.Called(listener)
}

func (m *MockForwarder) Listener() net.Listener {
	args := m.Called()
	return args.Get(0).(net.Listener)
}

func (m *MockForwarder) OpenForwardedChannel(ctx context.Context, origin net.Addr) (ssh.Channel, <-chan *ssh.Request, error) {
	args := m.Called(ctx, origin)
	if args.Get(0) == nil {
		return nil, nil, args.Error(2)
	}
	return args.Get(0).(ssh.Channel), args.Get(1).(<-chan *ssh.Request), args.Error(2)
}

type MockSessionRegistry struct {
	mock.Mock
}

func (m *MockSessionRegistry) Update(user string, oldKey, newKey types.SessionKey) error {
	args := m.Called(user, oldKey, newKey)
	return args.Error(0)
}

type MockChannel struct {
	mock.Mock
	data []byte
}

func (m *MockChannel) Read(b []byte) (n int, err error) {
	args := m.Called(b)
	return args.Int(0), args.Error(1)
}

func (m *MockChannel) Write(b []byte) (n int, err error) {
	m.data = append(m.data, b...)
	args := m.Called(b)
	return args.Int(0), args.Error(1)
}

func (m *MockChannel) Close() error {
	args := m.Called()
	return args.Error(0)
}

func (m *MockChannel) CloseWrite() error {
	args := m.Called()
	return args.Error(0)
}

func (m *MockChannel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
	args := m.Called(name, wantReply, payload)
	return args.Bool(0), args.Error(1)
}

func (m *MockChannel) Stderr() io.ReadWriter {
	args := m.Called()
	if args.Get(0) == nil {
		return nil
	}
	return args.Get(0).(io.ReadWriter)
}

type MockCloser struct {
	mock.Mock
}

func (m *MockCloser) Close() error { return m.Called().Error(0) }

func TestNew(t *testing.T) {
	tests := []struct {
		name string
		user string
	}{
		{
			name: "creates interaction with default mode",
			user: "testuser",
		},
		{
			name: "creates interaction for different user",
			user: "anotheruser",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, tt.user, mockCloser.Close)

			assert.NotNil(t, mockInteraction)
		})
	}
}

func TestInteraction_SetMode(t *testing.T) {
	tests := []struct {
		name string
		mode types.InteractiveMode
	}{
		{
			name: "set headless mode",
			mode: types.InteractiveModeHEADLESS,
		},
		{
			name: "set interactive mode",
			mode: types.InteractiveModeINTERACTIVE,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)
			mockInteraction.SetMode(tt.mode)

			assert.Equal(t, tt.mode, mockInteraction.Mode())
		})
	}
}

func TestInteraction_Mode(t *testing.T) {
	tests := []struct {
		name     string
		setMode  types.InteractiveMode
		expected types.InteractiveMode
	}{
		{
			name:     "mode returns set value",
			setMode:  types.InteractiveModeINTERACTIVE,
			expected: types.InteractiveModeINTERACTIVE,
		},
		{
			name:     "mode returns headless value",
			setMode:  types.InteractiveModeHEADLESS,
			expected: types.InteractiveModeHEADLESS,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)

			mockInteraction.SetMode(tt.setMode)
			assert.Equal(t, tt.expected, mockInteraction.Mode())
		})
	}
}

func TestInteraction_Send(t *testing.T) {
	tests := []struct {
		name          string
		message       string
		setupChannel  bool
		channelReturn int
		channelError  error
		wantError     bool
	}{
		{
			name:          "send message successfully",
			message:       "test message",
			setupChannel:  true,
			channelReturn: 12,
			channelError:  nil,
			wantError:     false,
		},
		{
			name:          "send message with channel error",
			message:       "test message",
			setupChannel:  true,
			channelReturn: 0,
			channelError:  errors.New("channel write error"),
			wantError:     true,
		},
		{
			name:         "send message without channel",
			message:      "test message",
			setupChannel: false,
			wantError:    false,
		},
		{
			name:          "send empty message",
			message:       "",
			setupChannel:  true,
			channelReturn: 0,
			channelError:  nil,
			wantError:     false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)

			if tt.setupChannel {
				mockChannel := &MockChannel{}
				mockChannel.On("Write", []byte(tt.message)).Return(tt.channelReturn, tt.channelError)
				mockInteraction.SetChannel(mockChannel)
			}

			err := mockInteraction.Send(tt.message)

			if tt.wantError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestInteraction_SetWH(t *testing.T) {
	tests := []struct {
		name   string
		width  int
		height int
	}{
		{
			name:   "set large window size",
			width:  100,
			height: 50,
		},
		{
			name:   "set medium window size",
			width:  80,
			height: 24,
		},
		{
			name:   "set small window size",
			width:  20,
			height: 10,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)

			mockInteraction.SetWH(tt.width, tt.height)
		})
	}
}

func TestInteraction_SetChannel(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	mockCloser := &MockCloser{}
	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)

	mockChannel := &MockChannel{}
	mockInteraction.SetChannel(mockChannel)

	mockChannel.On("Write", []byte("test")).Return(4, nil)
	err := mockInteraction.Send("test")
	assert.NoError(t, err)
}

func TestInteraction_Redraw(t *testing.T) {
	tests := []struct {
		name        string
		description string
	}{
		{
			name:        "redraw interaction",
			description: "should not panic when calling redraw",
		},
		{
			name:        "redraw multiple times",
			description: "should handle multiple redraws",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)

			mockInteraction.Redraw()
		})
	}
}

func TestInteraction_Start(t *testing.T) {
	tests := []struct {
		name       string
		mode       types.InteractiveMode
		tlsEnabled bool
		tunnelType types.TunnelType
		port       uint16
	}{
		{
			name:       "start in headless mode - should return immediately",
			mode:       types.InteractiveModeHEADLESS,
			tlsEnabled: false,
			tunnelType: types.TunnelTypeHTTP,
			port:       8080,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)
			mockInteraction.SetMode(tt.mode)

			mockConfig.On("Domain").Return("tunnl.live")
			mockConfig.On("TLSEnabled").Return(tt.tlsEnabled)
			mockForwarder.On("TunnelType").Return(tt.tunnelType)
			mockForwarder.On("ForwardedPort").Return(tt.port)

			mockInteraction.Start()
		})
	}
}

func TestModel_Update(t *testing.T) {
	tests := []struct {
		name              string
		msg               tea.Msg
		showingComingSoon bool
		editingSlug       bool
		showingCommands   bool
		width             int
		height            int
		expectedWidth     int
		expectedHeight    int
		expectedQuit      bool
	}{
		{
			name:              "tick message clears coming soon",
			msg:               tickMsg{},
			showingComingSoon: true,
			editingSlug:       false,
			showingCommands:   false,
			expectedQuit:      false,
		},
		{
			name:           "window size message - large screen",
			msg:            tea.WindowSizeMsg{Width: 100, Height: 50},
			expectedWidth:  100,
			expectedHeight: 50,
			expectedQuit:   false,
		},
		{
			name:           "window size message - small screen",
			msg:            tea.WindowSizeMsg{Width: 60, Height: 20},
			expectedWidth:  60,
			expectedHeight: 20,
			expectedQuit:   false,
		},
		{
			name:         "quit message",
			msg:          tea.QuitMsg{},
			expectedQuit: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)

			m := &model{
				randomizer:        mockRandom,
				domain:            "tunnl.live",
				protocol:          "http",
				tunnelType:        types.TunnelTypeHTTP,
				port:              8080,
				commandList:       list.New([]list.Item{}, list.NewDefaultDelegate(), 80, 20),
				interaction:       mockInteraction.(*interaction),
				showingComingSoon: tt.showingComingSoon,
				editingSlug:       tt.editingSlug,
				showingCommands:   tt.showingCommands,
				width:             tt.width,
				height:            tt.height,
				keymap: keymap{
					quit: key.NewBinding(
						key.WithKeys("q", "ctrl+c"),
						key.WithHelp("q", "quit"),
					),
					command: key.NewBinding(
						key.WithKeys("c"),
						key.WithHelp("c", "commands"),
					),
					random: key.NewBinding(
						key.WithKeys("ctrl+r"),
						key.WithHelp("ctrl+r", "random"),
					),
				},
			}

			result, _ := m.Update(tt.msg)
			updatedModel := result.(*model)

			if tt.expectedQuit {
				assert.True(t, updatedModel.quitting)
			}

			if windowMsg, ok := tt.msg.(tea.WindowSizeMsg); ok {
				assert.Equal(t, windowMsg.Width, updatedModel.width)
				assert.Equal(t, windowMsg.Height, updatedModel.height)
			}

			if _, ok := tt.msg.(tickMsg); ok && tt.showingComingSoon {
				assert.False(t, updatedModel.showingComingSoon)
			}
		})
	}
}

func TestModel_View(t *testing.T) {
	tests := []struct {
		name              string
		quitting          bool
		showingComingSoon bool
		editingSlug       bool
		showingCommands   bool
		expectedEmpty     bool
	}{
		{
			name:          "quitting returns empty string",
			quitting:      true,
			expectedEmpty: true,
		},
		{
			name:              "showing coming soon view",
			showingComingSoon: true,
			expectedEmpty:     false,
		},
		{
			name:          "editing slug view",
			editingSlug:   true,
			expectedEmpty: false,
		},
		{
			name:            "showing commands view",
			showingCommands: true,
			expectedEmpty:   false,
		},
		{
			name:          "dashboard view (default)",
			expectedEmpty: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)

			mockSlug.On("String").Return("test-slug")

			m := &model{
				randomizer:        mockRandom,
				domain:            "tunnl.live",
				protocol:          "http",
				tunnelType:        types.TunnelTypeHTTP,
				port:              8080,
				commandList:       list.New([]list.Item{}, list.NewDefaultDelegate(), 80, 20),
				interaction:       mockInteraction.(*interaction),
				quitting:          tt.quitting,
				showingComingSoon: tt.showingComingSoon,
				editingSlug:       tt.editingSlug,
				showingCommands:   tt.showingCommands,
				keymap: keymap{
					quit: key.NewBinding(
						key.WithKeys("q", "ctrl+c"),
						key.WithHelp("q", "quit"),
					),
					command: key.NewBinding(
						key.WithKeys("c"),
						key.WithHelp("c", "commands"),
					),
					random: key.NewBinding(
						key.WithKeys("ctrl+r"),
						key.WithHelp("ctrl+r", "random"),
					),
				},
			}

			view := m.View()

			if tt.expectedEmpty {
				assert.Empty(t, view)
			} else {
				assert.NotEmpty(t, view)
			}
		})
	}
}

func TestInteraction_Integration(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	closeCallCount := 0
	closeFunc := func() error {
		closeCallCount++
		return nil
	}
	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
	assert.NotNil(t, mockInteraction)

	mockInteraction.SetMode(types.InteractiveModeINTERACTIVE)
	assert.Equal(t, types.InteractiveModeINTERACTIVE, mockInteraction.Mode())

	mockChannel := &MockChannel{}
	mockInteraction.SetChannel(mockChannel)

	mockChannel.On("Write", []byte("hello")).Return(5, nil)
	err := mockInteraction.Send("hello")
	assert.NoError(t, err)
	mockChannel.AssertExpectations(t)

	mockInteraction.SetWH(80, 24)

	mockInteraction.Redraw()
}

func TestModel_Update_KeyMessages(t *testing.T) {
	tests := []struct {
		name              string
		key               tea.KeyMsg
		showingComingSoon bool
		editingSlug       bool
		showingCommands   bool
		description       string
	}{
		{
			name:              "key press while showing coming soon",
			key:               tea.KeyMsg{Type: tea.KeyEnter},
			showingComingSoon: true,
			description:       "should call comingSoonUpdate",
		},
		{
			name:        "key press while editing slug",
			key:         tea.KeyMsg{Type: tea.KeyEnter},
			editingSlug: true,
			description: "should call slugUpdate",
		},
		{
			name:            "key press while showing commands",
			key:             tea.KeyMsg{Type: tea.KeyEnter},
			showingCommands: true,
			description:     "should call commandsUpdate",
		},
		{
			name:        "key press in dashboard view",
			key:         tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'c'}},
			description: "should call dashboardUpdate",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "user", mockCloser.Close)

			mockSlug.On("String").Return("test-slug").Maybe()
			mockSessionRegistry.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil)

			m := &model{
				randomizer:        mockRandom,
				domain:            "tunnl.live",
				protocol:          "http",
				tunnelType:        types.TunnelTypeHTTP,
				port:              8080,
				commandList:       list.New([]list.Item{}, list.NewDefaultDelegate(), 80, 20),
				interaction:       mockInteraction.(*interaction),
				showingComingSoon: tt.showingComingSoon,
				editingSlug:       tt.editingSlug,
				showingCommands:   tt.showingCommands,
				keymap: keymap{
					quit: key.NewBinding(
						key.WithKeys("q", "ctrl+c"),
						key.WithHelp("q", "quit"),
					),
					command: key.NewBinding(
						key.WithKeys("c"),
						key.WithHelp("c", "commands"),
					),
					random: key.NewBinding(
						key.WithKeys("ctrl+r"),
						key.WithHelp("ctrl+r", "random"),
					),
				},
			}

			result, _ := m.Update(tt.key)
			assert.NotNil(t, result)
		})
	}
}

func TestModel_SlugUpdate(t *testing.T) {
	tests := []struct {
		name           string
		tunnelType     types.TunnelType
		keyMsg         tea.KeyMsg
		inputValue     string
		setupMocks     func(*MockSessionRegistry, *MockSlug, *MockRandom)
		expectedEdit   bool
		expectedError  string
		shouldSetValue bool
	}{
		{
			name:         "escape key cancels editing",
			tunnelType:   types.TunnelTypeHTTP,
			keyMsg:       tea.KeyMsg{Type: tea.KeyEsc},
			setupMocks:   func(msr *MockSessionRegistry, ms *MockSlug, mr *MockRandom) {},
			expectedEdit: false,
		},
		{
			name:         "ctrl+c cancels editing",
			tunnelType:   types.TunnelTypeHTTP,
			keyMsg:       tea.KeyMsg{Type: tea.KeyCtrlC},
			setupMocks:   func(msr *MockSessionRegistry, ms *MockSlug, mr *MockRandom) {},
			expectedEdit: false,
		},
		{
			name:       "enter key saves valid slug",
			tunnelType: types.TunnelTypeHTTP,
			keyMsg:     tea.KeyMsg{Type: tea.KeyEnter},
			inputValue: "my-custom-slug",
			setupMocks: func(msr *MockSessionRegistry, ms *MockSlug, mr *MockRandom) {
				ms.On("String").Return("old-slug")
				msr.On("Update", "testuser",
					types.SessionKey{Id: "old-slug", Type: types.TunnelTypeHTTP},
					types.SessionKey{Id: "my-custom-slug", Type: types.TunnelTypeHTTP},
				).Return(nil)
			},
			expectedEdit:  false,
			expectedError: "",
		},
		{
			name:       "enter key with error shows error message",
			tunnelType: types.TunnelTypeHTTP,
			keyMsg:     tea.KeyMsg{Type: tea.KeyEnter},
			inputValue: "invalid",
			setupMocks: func(msr *MockSessionRegistry, ms *MockSlug, mr *MockRandom) {
				ms.On("String").Return("old-slug")
				msr.On("Update", "testuser",
					types.SessionKey{Id: "old-slug", Type: types.TunnelTypeHTTP},
					types.SessionKey{Id: "invalid", Type: types.TunnelTypeHTTP},
				).Return(assert.AnError)
			},
			expectedEdit:  true,
			expectedError: assert.AnError.Error(),
		},
		{
			name:       "ctrl+r generates random slug",
			tunnelType: types.TunnelTypeHTTP,
			keyMsg:     tea.KeyMsg{Type: tea.KeyCtrlR},
			setupMocks: func(msr *MockSessionRegistry, ms *MockSlug, mr *MockRandom) {
				mr.On("String", 20).Return("random-generated-slug", nil)
			},
			expectedEdit:   true,
			shouldSetValue: true,
		},
		{
			name:       "ctrl+r with error does nothing",
			tunnelType: types.TunnelTypeHTTP,
			keyMsg:     tea.KeyMsg{Type: tea.KeyCtrlR},
			setupMocks: func(msr *MockSessionRegistry, ms *MockSlug, mr *MockRandom) {
				mr.On("String", 20).Return("", assert.AnError)
			},
			expectedEdit: true,
		},
		{
			name:         "regular key updates input",
			tunnelType:   types.TunnelTypeHTTP,
			keyMsg:       tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}},
			setupMocks:   func(msr *MockSessionRegistry, ms *MockSlug, mr *MockRandom) {},
			expectedEdit: true,
		},
		{
			name:         "tcp tunnel exits editing immediately",
			tunnelType:   types.TunnelTypeTCP,
			keyMsg:       tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}},
			setupMocks:   func(msr *MockSessionRegistry, ms *MockSlug, mr *MockRandom) {},
			expectedEdit: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			ti := textinput.New()
			ti.SetValue(tt.inputValue)

			m := &model{
				randomizer:  mockRandom,
				domain:      "tunnl.live",
				protocol:    "http",
				tunnelType:  tt.tunnelType,
				port:        8080,
				commandList: list.New([]list.Item{}, list.NewDefaultDelegate(), 80, 20),
				slugInput:   ti,
				editingSlug: true,
				interaction: mockInteraction.(*interaction),
				keymap: keymap{
					quit: key.NewBinding(
						key.WithKeys("q", "ctrl+c"),
						key.WithHelp("q", "quit"),
					),
					command: key.NewBinding(
						key.WithKeys("c"),
						key.WithHelp("c", "commands"),
					),
					random: key.NewBinding(
						key.WithKeys("ctrl+r"),
						key.WithHelp("ctrl+r", "random"),
					),
				},
			}

			tt.setupMocks(mockSessionRegistry, mockSlug, mockRandom)

			result, _ := m.slugUpdate(tt.keyMsg)
			resultModel := result.(*model)

			assert.Equal(t, tt.expectedEdit, resultModel.editingSlug)
			if tt.expectedError != "" {
				assert.Equal(t, tt.expectedError, resultModel.slugError)
			} else if !tt.expectedEdit {
				assert.Equal(t, "", resultModel.slugError)
			}

			mockSessionRegistry.AssertExpectations(t)
			mockSlug.AssertExpectations(t)
			mockRandom.AssertExpectations(t)
		})
	}
}

func TestModel_SlugView(t *testing.T) {
	tests := []struct {
		name       string
		width      int
		tunnelType types.TunnelType
		slugError  string
		contains   string
	}{
		{
			name:       "http tunnel - large screen",
			width:      100,
			tunnelType: types.TunnelTypeHTTP,
			contains:   "Subdomain",
		},
		{
			name:       "http tunnel - small screen",
			width:      50,
			tunnelType: types.TunnelTypeHTTP,
			contains:   "Subdomain",
		},
		{
			name:       "http tunnel - tiny screen",
			width:      30,
			tunnelType: types.TunnelTypeHTTP,
			contains:   "Subdomain",
		},
		{
			name:       "http tunnel with error",
			width:      100,
			tunnelType: types.TunnelTypeHTTP,
			slugError:  "Slug already exists",
			contains:   "Slug already exists",
		},
		{
			name:       "tcp tunnel - large screen",
			width:      100,
			tunnelType: types.TunnelTypeTCP,
			contains:   "TCP",
		},
		{
			name:       "tcp tunnel - small screen",
			width:      50,
			tunnelType: types.TunnelTypeTCP,
			contains:   "TCP",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			ti := textinput.New()
			ti.SetValue("test-slug")

			m := &model{
				randomizer:  mockRandom,
				domain:      "tunnl.live",
				protocol:    "http",
				tunnelType:  tt.tunnelType,
				port:        8080,
				slugInput:   ti,
				slugError:   tt.slugError,
				interaction: mockInteraction.(*interaction),
				width:       tt.width,
			}

			view := m.slugView()
			assert.NotEmpty(t, view)
			assert.Contains(t, view, tt.contains)
		})
	}
}

func TestModel_ComingSoonUpdate(t *testing.T) {
	tests := []struct {
		name   string
		keyMsg tea.KeyMsg
	}{
		{
			name:   "any key dismisses coming soon",
			keyMsg: tea.KeyMsg{Type: tea.KeyEnter},
		},
		{
			name:   "escape key dismisses",
			keyMsg: tea.KeyMsg{Type: tea.KeyEsc},
		},
		{
			name:   "space key dismisses",
			keyMsg: tea.KeyMsg{Type: tea.KeySpace},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			m := &model{
				interaction:       mockInteraction.(*interaction),
				showingComingSoon: true,
			}

			result, _ := m.comingSoonUpdate(tt.keyMsg)
			resultModel := result.(*model)

			assert.False(t, resultModel.showingComingSoon)
		})
	}
}

func TestModel_ComingSoonView(t *testing.T) {
	tests := []struct {
		name  string
		width int
	}{
		{
			name:  "large screen",
			width: 100,
		},
		{
			name:  "medium screen",
			width: 60,
		},
		{
			name:  "small screen",
			width: 50,
		},
		{
			name:  "tiny screen",
			width: 30,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			m := &model{
				interaction: mockInteraction.(*interaction),
				width:       tt.width,
			}

			view := m.comingSoonView()
			assert.NotEmpty(t, view)
			assert.Contains(t, view, "Coming")
		})
	}
}

func TestModel_CommandsUpdate(t *testing.T) {
	tests := []struct {
		name             string
		keyMsg           tea.KeyMsg
		selectedItem     list.Item
		expectCommands   bool
		expectEditSlug   bool
		expectComingSoon bool
	}{
		{
			name:           "escape key closes commands",
			keyMsg:         tea.KeyMsg{Type: tea.KeyEsc},
			expectCommands: false,
		},
		{
			name:           "q key closes commands",
			keyMsg:         tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'q'}},
			expectCommands: false,
		},
		{
			name:           "enter on slug command starts editing",
			keyMsg:         tea.KeyMsg{Type: tea.KeyEnter},
			selectedItem:   commandItem{name: "slug", desc: "Set custom subdomain"},
			expectCommands: false,
			expectEditSlug: true,
		},
		{
			name:             "enter on tunnel-type shows coming soon",
			keyMsg:           tea.KeyMsg{Type: tea.KeyEnter},
			selectedItem:     commandItem{name: "tunnel-type", desc: "Change tunnel type"},
			expectCommands:   false,
			expectComingSoon: true,
		},
		{
			name:           "arrow key navigates list",
			keyMsg:         tea.KeyMsg{Type: tea.KeyDown},
			expectCommands: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			mockSlug.On("String").Return("current-slug").Maybe()

			items := []list.Item{
				commandItem{name: "slug", desc: "Set custom subdomain"},
				commandItem{name: "tunnel-type", desc: "Change tunnel type"},
			}

			delegate := list.NewDefaultDelegate()
			commandList := list.New(items, delegate, 80, 20)
			if tt.selectedItem != nil {
				for i, item := range items {
					if item.(commandItem).name == tt.selectedItem.(commandItem).name {
						commandList.Select(i)
						break
					}
				}
			}

			ti := textinput.New()

			m := &model{
				randomizer:      mockRandom,
				interaction:     mockInteraction.(*interaction),
				showingCommands: true,
				commandList:     commandList,
				slugInput:       ti,
				keymap: keymap{
					quit: key.NewBinding(
						key.WithKeys("q", "ctrl+c"),
						key.WithHelp("q", "quit"),
					),
					command: key.NewBinding(
						key.WithKeys("c"),
						key.WithHelp("c", "commands"),
					),
					random: key.NewBinding(
						key.WithKeys("ctrl+r"),
						key.WithHelp("ctrl+r", "random"),
					),
				},
			}

			result, _ := m.commandsUpdate(tt.keyMsg)
			resultModel := result.(*model)

			assert.Equal(t, tt.expectCommands, resultModel.showingCommands)
			if tt.expectEditSlug {
				assert.True(t, resultModel.editingSlug)
			}
			if tt.expectComingSoon {
				assert.True(t, resultModel.showingComingSoon)
			}
		})
	}
}

func TestModel_CommandsView(t *testing.T) {
	tests := []struct {
		name  string
		width int
	}{
		{
			name:  "large screen",
			width: 100,
		},
		{
			name:  "medium screen",
			width: 60,
		},
		{
			name:  "small screen",
			width: 50,
		},
		{
			name:  "tiny screen",
			width: 30,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			items := []list.Item{
				commandItem{name: "slug", desc: "Set custom subdomain"},
				commandItem{name: "tunnel-type", desc: "Change tunnel type"},
			}

			delegate := list.NewDefaultDelegate()
			commandList := list.New(items, delegate, 80, 20)

			m := &model{
				interaction: mockInteraction.(*interaction),
				commandList: commandList,
				width:       tt.width,
			}

			view := m.commandsView()
			assert.NotEmpty(t, view)
			assert.Contains(t, view, "Commands")
		})
	}
}

func TestModel_DashboardUpdate(t *testing.T) {
	tests := []struct {
		name           string
		keyMsg         tea.KeyMsg
		expectQuit     bool
		expectCommands bool
	}{
		{
			name:       "q key quits",
			keyMsg:     tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'q'}},
			expectQuit: true,
		},
		{
			name:       "ctrl+c quits",
			keyMsg:     tea.KeyMsg{Type: tea.KeyCtrlC},
			expectQuit: true,
		},
		{
			name:           "c key opens commands",
			keyMsg:         tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'c'}},
			expectCommands: true,
		},
		{
			name:   "other keys do nothing",
			keyMsg: tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'x'}},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			m := &model{
				interaction: mockInteraction.(*interaction),
				keymap: keymap{
					quit: key.NewBinding(
						key.WithKeys("q", "ctrl+c"),
						key.WithHelp("q", "quit"),
					),
					command: key.NewBinding(
						key.WithKeys("c"),
						key.WithHelp("c", "commands"),
					),
					random: key.NewBinding(
						key.WithKeys("ctrl+r"),
						key.WithHelp("ctrl+r", "random"),
					),
				},
			}

			result, _ := m.dashboardUpdate(tt.keyMsg)
			resultModel := result.(*model)

			if tt.expectQuit {
				assert.True(t, resultModel.quitting)
			}
			if tt.expectCommands {
				assert.True(t, resultModel.showingCommands)
			}
		})
	}
}

func TestModel_DashboardView(t *testing.T) {
	tests := []struct {
		name       string
		width      int
		tunnelType types.TunnelType
		protocol   string
		port       uint16
		contains   string
	}{
		{
			name:       "http tunnel - large screen",
			width:      100,
			tunnelType: types.TunnelTypeHTTP,
			protocol:   "http",
			contains:   "http",
		},
		{
			name:       "https tunnel - large screen",
			width:      100,
			tunnelType: types.TunnelTypeHTTP,
			protocol:   "https",
			contains:   "https",
		},
		{
			name:       "tcp tunnel - large screen",
			width:      100,
			tunnelType: types.TunnelTypeTCP,
			port:       8080,
			contains:   "tcp",
		},
		{
			name:       "http tunnel - medium screen",
			width:      70,
			tunnelType: types.TunnelTypeHTTP,
			protocol:   "http",
			contains:   "http",
		},
		{
			name:       "http tunnel - small screen",
			width:      50,
			tunnelType: types.TunnelTypeHTTP,
			protocol:   "http",
			contains:   "http",
		},
		{
			name:       "http tunnel - tiny screen",
			width:      30,
			tunnelType: types.TunnelTypeHTTP,
			protocol:   "http",
			contains:   "http",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			mockSlug.On("String").Return("test-slug")

			m := &model{
				randomizer:  mockRandom,
				domain:      "tunnl.live",
				protocol:    tt.protocol,
				tunnelType:  tt.tunnelType,
				port:        tt.port,
				interaction: mockInteraction.(*interaction),
				width:       tt.width,
			}

			view := m.dashboardView()
			assert.NotEmpty(t, view)
			assert.Contains(t, view, tt.contains)
		})
	}
}

func TestGetResponsiveWidth(t *testing.T) {
	tests := []struct {
		name        string
		screenWidth int
		padding     int
		minWidth    int
		maxWidth    int
		expected    int
	}{
		{
			name:        "screen wider than max",
			screenWidth: 100,
			padding:     10,
			minWidth:    20,
			maxWidth:    60,
			expected:    60,
		},
		{
			name:        "screen narrower than min",
			screenWidth: 30,
			padding:     10,
			minWidth:    40,
			maxWidth:    80,
			expected:    40,
		},
		{
			name:        "screen within range",
			screenWidth: 70,
			padding:     10,
			minWidth:    20,
			maxWidth:    80,
			expected:    60,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := getResponsiveWidth(tt.screenWidth, tt.padding, tt.minWidth, tt.maxWidth)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestShouldUseCompactLayout(t *testing.T) {
	tests := []struct {
		name      string
		width     int
		threshold int
		expected  bool
	}{
		{
			name:      "width below threshold",
			width:     50,
			threshold: 60,
			expected:  true,
		},
		{
			name:      "width at threshold",
			width:     60,
			threshold: 60,
			expected:  false,
		},
		{
			name:      "width above threshold",
			width:     70,
			threshold: 60,
			expected:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := shouldUseCompactLayout(tt.width, tt.threshold)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestTruncateString(t *testing.T) {
	tests := []struct {
		name      string
		input     string
		maxLength int
		expected  string
	}{
		{
			name:      "string shorter than max",
			input:     "short",
			maxLength: 10,
			expected:  "short",
		},
		{
			name:      "string equal to max",
			input:     "exactly10c",
			maxLength: 10,
			expected:  "exactly10c",
		},
		{
			name:      "string longer than max",
			input:     "this is a very long string",
			maxLength: 10,
			expected:  "this is...",
		},
		{
			name:      "very short max length",
			input:     "hello",
			maxLength: 3,
			expected:  "hel",
		},
		{
			name:      "max length less than 4",
			input:     "hello",
			maxLength: 2,
			expected:  "he",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := truncateString(tt.input, tt.maxLength)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestBuildURL(t *testing.T) {
	tests := []struct {
		name      string
		protocol  string
		subdomain string
		domain    string
		expected  string
	}{
		{
			name:      "http url",
			protocol:  "http",
			subdomain: "test",
			domain:    "tunnl.live",
			expected:  "http://test.tunnl.live",
		},
		{
			name:      "https url",
			protocol:  "https",
			subdomain: "api",
			domain:    "myapp.io",
			expected:  "https://api.myapp.io",
		},
		{
			name:      "custom subdomain",
			protocol:  "http",
			subdomain: "my-custom-slug",
			domain:    "tunnl.live",
			expected:  "http://my-custom-slug.tunnl.live",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := buildURL(tt.protocol, tt.subdomain, tt.domain)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestTickCmd(t *testing.T) {
	tests := []struct {
		name     string
		duration time.Duration
	}{
		{
			name:     "5 second tick",
			duration: 5 * time.Second,
		},
		{
			name:     "1 second tick",
			duration: 1 * time.Second,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := tickCmd(tt.duration)
			assert.NotNil(t, cmd)
		})
	}
}

func TestGetPaddingValue(t *testing.T) {
	tests := []struct {
		name          string
		isVeryCompact bool
		isCompact     bool
		expected      int
	}{
		{
			name:          "very compact layout",
			isVeryCompact: true,
			isCompact:     false,
			expected:      1,
		},
		{
			name:          "compact layout",
			isVeryCompact: false,
			isCompact:     true,
			expected:      1,
		},
		{
			name:          "normal layout",
			isVeryCompact: false,
			isCompact:     false,
			expected:      2,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := getPaddingValue(tt.isVeryCompact, tt.isCompact)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestGetMarginValue(t *testing.T) {
	tests := []struct {
		name         string
		isCompact    bool
		compactValue int
		normalValue  int
		expected     int
	}{
		{
			name:         "compact layout",
			isCompact:    true,
			compactValue: 1,
			normalValue:  2,
			expected:     1,
		},
		{
			name:         "normal layout",
			isCompact:    false,
			compactValue: 1,
			normalValue:  2,
			expected:     2,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := getMarginValue(tt.isCompact, tt.compactValue, tt.normalValue)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestCommandItem(t *testing.T) {
	tests := []struct {
		name     string
		item     commandItem
		wantName string
		wantDesc string
	}{
		{
			name:     "slug command",
			item:     commandItem{name: "slug", desc: "Set custom subdomain"},
			wantName: "slug",
			wantDesc: "Set custom subdomain",
		},
		{
			name:     "tunnel-type command",
			item:     commandItem{name: "tunnel-type", desc: "Change tunnel type"},
			wantName: "tunnel-type",
			wantDesc: "Change tunnel type",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.wantName, tt.item.FilterValue())
			assert.Equal(t, tt.wantName, tt.item.Title())
			assert.Equal(t, tt.wantDesc, tt.item.Description())
		})
	}
}

func TestModel_GetTunnelURL(t *testing.T) {
	tests := []struct {
		name       string
		tunnelType types.TunnelType
		protocol   string
		slug       string
		domain     string
		port       uint16
		expected   string
	}{
		{
			name:       "http tunnel",
			tunnelType: types.TunnelTypeHTTP,
			protocol:   "http",
			slug:       "my-app",
			domain:     "tunnl.live",
			expected:   "http://my-app.tunnl.live",
		},
		{
			name:       "https tunnel",
			tunnelType: types.TunnelTypeHTTP,
			protocol:   "https",
			slug:       "secure-app",
			domain:     "tunnl.live",
			expected:   "https://secure-app.tunnl.live",
		},
		{
			name:       "tcp tunnel",
			tunnelType: types.TunnelTypeTCP,
			domain:     "tunnl.live",
			port:       8080,
			expected:   "tcp://tunnl.live:8080",
		},
		{
			name:       "tcp tunnel with different port",
			tunnelType: types.TunnelTypeTCP,
			domain:     "tunnl.live",
			port:       3306,
			expected:   "tcp://tunnl.live:3306",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			mockCloser := &MockCloser{}
			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

			mockSlug.On("String").Return(tt.slug).Maybe()

			m := &model{
				domain:      tt.domain,
				protocol:    tt.protocol,
				tunnelType:  tt.tunnelType,
				port:        tt.port,
				interaction: mockInteraction.(*interaction),
			}

			result := m.getTunnelURL()
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestModel_Init(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	mockCloser := &MockCloser{}
	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", mockCloser.Close)

	m := &model{
		interaction: mockInteraction.(*interaction),
	}

	cmd := m.Init()
	assert.NotNil(t, cmd)
}

func TestInteraction_Start_Interactive(t *testing.T) {
	tests := []struct {
		name       string
		tlsEnabled bool
		tunnelType types.TunnelType
		port       uint16
		domain     string
	}{
		{
			name:       "interactive mode with http",
			tlsEnabled: false,
			tunnelType: types.TunnelTypeHTTP,
			port:       8080,
			domain:     "tunnl.live",
		},
		{
			name:       "interactive mode with https",
			tlsEnabled: true,
			tunnelType: types.TunnelTypeHTTP,
			port:       8443,
			domain:     "secure.tunnl.live",
		},
		{
			name:       "interactive mode with tcp",
			tlsEnabled: false,
			tunnelType: types.TunnelTypeTCP,
			port:       3306,
			domain:     "db.tunnl.live",
		},
		{
			name:       "interactive mode with tcp and tls enabled",
			tlsEnabled: true,
			tunnelType: types.TunnelTypeTCP,
			port:       5432,
			domain:     "postgres.tunnl.live",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			closeCallCount := 0
			closeFunc := func() error {
				closeCallCount++
				return nil
			}

			mockConfig.On("Domain").Return(tt.domain)
			mockConfig.On("TLSEnabled").Return(tt.tlsEnabled)
			mockForwarder.On("TunnelType").Return(tt.tunnelType)
			mockForwarder.On("ForwardedPort").Return(tt.port)
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
			mockInteraction.SetMode(types.InteractiveModeINTERACTIVE)

			mockChannel := &MockChannel{}
			mockChannel.On("Read", mock.Anything).Return(0, assert.AnError).Maybe()
			mockChannel.On("Write", mock.Anything).Return(0, nil).Maybe()
			mockInteraction.SetChannel(mockChannel)

			done := make(chan bool, 1)
			go func() {
				mockInteraction.Start()
				done <- true
			}()

			time.Sleep(50 * time.Millisecond)

			i := mockInteraction.(*interaction)
			i.Stop()

			select {
			case <-done:
			case <-time.After(2 * time.Second):
				t.Fatal("Start() did not complete in time")
			}

			assert.Equal(t, 1, closeCallCount, "close function should be called once")

			mockConfig.AssertExpectations(t)
			mockForwarder.AssertExpectations(t)
		})
	}
}

func TestInteraction_Start_ProtocolSelection(t *testing.T) {
	tests := []struct {
		name          string
		tlsEnabled    bool
		expectedProto string
	}{
		{
			name:          "http when TLS disabled",
			tlsEnabled:    false,
			expectedProto: "http",
		},
		{
			name:          "https when TLS enabled",
			tlsEnabled:    true,
			expectedProto: "https",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			closeFunc := func() error { return nil }

			mockConfig.On("Domain").Return("tunnl.live")
			mockConfig.On("TLSEnabled").Return(tt.tlsEnabled)
			mockForwarder.On("TunnelType").Return(types.TunnelTypeHTTP)
			mockForwarder.On("ForwardedPort").Return(uint16(8080))
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
			mockInteraction.SetMode(types.InteractiveModeINTERACTIVE)

			mockChannel := &MockChannel{}
			mockChannel.On("Read", mock.Anything).Return(0, assert.AnError).Maybe()
			mockChannel.On("Write", mock.Anything).Return(0, nil).Maybe()
			mockInteraction.SetChannel(mockChannel)

			go func() {
				mockInteraction.Start()
			}()

			time.Sleep(50 * time.Millisecond)

			i := mockInteraction.(*interaction)
			i.Stop()

			mockConfig.AssertExpectations(t)
			mockForwarder.AssertExpectations(t)
		})
	}
}

func TestInteraction_Stop(t *testing.T) {
	tests := []struct {
		name         string
		setupProgram bool
		description  string
	}{
		{
			name:         "stop with active program",
			setupProgram: true,
			description:  "should kill program and set to nil",
		},
		{
			name:         "stop without program",
			setupProgram: false,
			description:  "should not panic when program is nil",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			closeFunc := func() error { return nil }
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
			i := mockInteraction.(*interaction)

			if tt.setupProgram {
				mockConfig.On("Domain").Return("tunnl.live")
				mockConfig.On("TLSEnabled").Return(false)
				mockForwarder.On("TunnelType").Return(types.TunnelTypeHTTP)
				mockForwarder.On("ForwardedPort").Return(uint16(8080))

				mockInteraction.SetMode(types.InteractiveModeINTERACTIVE)
				mockChannel := &MockChannel{}
				mockChannel.On("Read", mock.Anything).Return(0, assert.AnError).Maybe()
				mockChannel.On("Write", mock.Anything).Return(0, nil).Maybe()
				mockInteraction.SetChannel(mockChannel)

				go func() {
					mockInteraction.Start()
				}()

				time.Sleep(50 * time.Millisecond)
			}

			assert.NotPanics(t, func() {
				i.Stop()
			})

			assert.Nil(t, i.program)

			select {
			case <-i.ctx.Done():
			case <-time.After(100 * time.Millisecond):
				t.Fatal("context should be cancelled after Stop()")
			}
		})
	}
}

func TestInteraction_Start_CommandListSetup(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	closeFunc := func() error { return nil }

	mockConfig.On("Domain").Return("tunnl.live")
	mockConfig.On("TLSEnabled").Return(false)
	mockForwarder.On("TunnelType").Return(types.TunnelTypeHTTP)
	mockForwarder.On("ForwardedPort").Return(uint16(8080))

	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
	mockInteraction.SetMode(types.InteractiveModeINTERACTIVE)

	mockChannel := &MockChannel{}
	mockChannel.On("Read", mock.Anything).Return(0, nil)
	mockChannel.On("Write", mock.Anything).Return(0, nil)
	mockInteraction.SetChannel(mockChannel)

	go func() {
		mockInteraction.Start()
	}()

	time.Sleep(50 * time.Millisecond)

	i := mockInteraction.(*interaction)

	assert.NotNil(t, i.program, "program should be initialized")

	i.Stop()
}

func TestInteraction_Start_TextInputSetup(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	closeFunc := func() error { return nil }

	mockConfig.On("Domain").Return("tunnl.live")
	mockConfig.On("TLSEnabled").Return(false)
	mockForwarder.On("TunnelType").Return(types.TunnelTypeHTTP)
	mockForwarder.On("ForwardedPort").Return(uint16(8080))
	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
	mockInteraction.SetMode(types.InteractiveModeINTERACTIVE)

	mockChannel := &MockChannel{}
	mockChannel.On("Read", mock.Anything).Return(0, assert.AnError).Maybe()
	mockChannel.On("Write", mock.Anything).Return(0, nil).Maybe()
	mockInteraction.SetChannel(mockChannel)

	go func() {
		mockInteraction.Start()
	}()

	time.Sleep(50 * time.Millisecond)

	i := mockInteraction.(*interaction)
	i.Stop()

	mockConfig.AssertExpectations(t)
	mockForwarder.AssertExpectations(t)
}

func TestInteraction_Start_CleanupOnExit(t *testing.T) {
	tests := []struct {
		name              string
		closeFunc         CloseFunc
		expectCloseCalled bool
	}{
		{
			name: "cleanup calls close function",
			closeFunc: func() error {
				return nil
			},
			expectCloseCalled: true,
		},
		{
			name:              "cleanup with nil close function",
			closeFunc:         nil,
			expectCloseCalled: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}

			closeCallCount := 0
			var closeFunc CloseFunc
			if tt.closeFunc != nil {
				closeFunc = func() error {
					closeCallCount++
					return tt.closeFunc()
				}
			}

			mockConfig.On("Domain").Return("tunnl.live")
			mockConfig.On("TLSEnabled").Return(false)
			mockForwarder.On("TunnelType").Return(types.TunnelTypeHTTP)
			mockForwarder.On("ForwardedPort").Return(uint16(8080))
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
			mockInteraction.SetMode(types.InteractiveModeINTERACTIVE)

			mockChannel := &MockChannel{}
			mockChannel.On("Read", mock.Anything).Return(0, assert.AnError).Maybe()
			mockChannel.On("Write", mock.Anything).Return(0, nil).Maybe()
			mockInteraction.SetChannel(mockChannel)

			done := make(chan bool, 1)
			go func() {
				mockInteraction.Start()
				done <- true
			}()

			time.Sleep(50 * time.Millisecond)

			i := mockInteraction.(*interaction)
			i.Stop()

			select {
			case <-done:
			case <-time.After(2 * time.Second):
				t.Fatal("Start() did not complete")
			}

			if tt.expectCloseCalled {
				assert.Equal(t, 1, closeCallCount, "close function should be called")
			} else {
				assert.Equal(t, 0, closeCallCount, "close function should not be called when nil")
			}
		})
	}
}

func TestInteraction_Start_WithDifferentChannels(t *testing.T) {
	tests := []struct {
		name         string
		setupChannel bool
	}{
		{
			name:         "start with channel set",
			setupChannel: true,
		},
		{
			name:         "start with nil channel",
			setupChannel: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockRandom := &MockRandom{}
			mockConfig := &MockConfig{}
			mockSlug := &MockSlug{}
			mockForwarder := &MockForwarder{}
			mockSessionRegistry := &MockSessionRegistry{}
			closeFunc := func() error { return nil }

			mockConfig.On("Domain").Return("tunnl.live")
			mockConfig.On("TLSEnabled").Return(false)
			mockForwarder.On("TunnelType").Return(types.TunnelTypeHTTP)
			mockForwarder.On("ForwardedPort").Return(uint16(8080))
			mockSlug.On("String").Return("test-slug")

			mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
			mockInteraction.SetMode(types.InteractiveModeINTERACTIVE)

			if tt.setupChannel {
				mockChannel := &MockChannel{}
				mockChannel.On("Read", mock.Anything).Return(0, assert.AnError).Maybe()
				mockChannel.On("Write", mock.Anything).Return(0, nil).Maybe()
				mockInteraction.SetChannel(mockChannel)
			}

			go func() {
				mockInteraction.Start()
			}()

			time.Sleep(50 * time.Millisecond)

			i := mockInteraction.(*interaction)
			i.Stop()

			mockConfig.AssertExpectations(t)
			mockForwarder.AssertExpectations(t)
		})
	}
}

func TestInteraction_Stop_ContextCancellation(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	closeFunc := func() error { return nil }
	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
	i := mockInteraction.(*interaction)

	select {
	case <-i.ctx.Done():
		t.Fatal("context should not be cancelled initially")
	default:
	}

	i.Stop()

	select {
	case <-i.ctx.Done():
	case <-time.After(100 * time.Millisecond):
		t.Fatal("context should be cancelled after Stop()")
	}

	assert.NotPanics(t, func() {
		i.Stop()
	})
}

func TestInteraction_Stop_MultipleCallsSafe(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	closeFunc := func() error { return nil }
	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
	i := mockInteraction.(*interaction)

	assert.NotPanics(t, func() {
		i.Stop()
		i.Stop()
		i.Stop()
	})

	assert.Nil(t, i.program)
}

func TestInteraction_Start_HeadlessMode_NoOp(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	closeFunc := func() error { return nil }
	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
	mockInteraction.SetMode(types.InteractiveModeHEADLESS)

	done := make(chan bool, 1)
	go func() {
		mockInteraction.Start()
		done <- true
	}()

	select {
	case <-done:
	case <-time.After(100 * time.Millisecond):
		t.Fatal("headless mode should return immediately")
	}

	i := mockInteraction.(*interaction)
	assert.Nil(t, i.program, "program should not be created in headless mode")

	mockConfig.AssertNotCalled(t, "Domain")
	mockConfig.AssertNotCalled(t, "TLSEnabled")
	mockForwarder.AssertNotCalled(t, "TunnelType")
	mockForwarder.AssertNotCalled(t, "ForwardedPort")
}

func TestInteraction_New_ContextInitialization(t *testing.T) {
	mockRandom := &MockRandom{}
	mockConfig := &MockConfig{}
	mockSlug := &MockSlug{}
	mockForwarder := &MockForwarder{}
	mockSessionRegistry := &MockSessionRegistry{}
	closeFunc := func() error { return nil }
	mockSlug.On("String").Return("test-slug")

	mockInteraction := New(mockRandom, mockConfig, mockSlug, mockForwarder, mockSessionRegistry, "testuser", closeFunc)
	i := mockInteraction.(*interaction)

	assert.NotNil(t, i.ctx, "context should be initialized")
	assert.NotNil(t, i.cancel, "cancel function should be initialized")

	select {
	case <-i.ctx.Done():
		t.Fatal("context should not be cancelled initially")
	default:
	}
}
