package intercept

import (
	"charm.land/bubbles/v2/key"
	tea "charm.land/bubbletea/v2"
	"github.com/anotherhadi/spilltea/internal/intercept"
	"github.com/anotherhadi/spilltea/internal/keys"
	diffUI "github.com/anotherhadi/spilltea/internal/ui/diff"
	replayUI "github.com/anotherhadi/spilltea/internal/ui/replay"
	"github.com/anotherhadi/spilltea/internal/util"
)

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmds []tea.Cmd

	// Route non-key messages to textarea when editing so internal
	// textarea messages (e.g. clipboard paste) are handled correctly.
	if m.editing {
		if _, ok := msg.(tea.KeyPressMsg); !ok {
			var taCmd tea.Cmd
			m.textarea, taCmd = m.textarea.Update(msg)
			cmds = append(cmds, taCmd)
		}
	}

	switch msg := msg.(type) {
	case intercept.RequestArrivedMsg:
		if !m.interceptEnabled {
			m.broker.Decide(msg.Req, intercept.Forward)
			break
		}
		wasEmpty := len(m.queue) == 0
		m.queue = append(m.queue, msg.Req)
		m.hasUnread = true
		m.refreshListViewport()
		if wasEmpty && (!m.captureResponse || m.focusedPanel == panelRequests) {
			m.refreshBody()
		}

	case intercept.ResponseArrivedMsg:
		wasEmpty := len(m.responseQueue) == 0
		m.responseQueue = append(m.responseQueue, msg.Resp)
		m.refreshResponseListViewport()
		if wasEmpty && m.captureResponse && m.focusedPanel == panelResponses {
			m.refreshBody()
		}

	case util.EditorFinishedMsg:
		if msg.Err == nil && msg.Content != "" {
			m.textarea.SetValue(msg.Content)
			m.refreshBodyViewport()
		}

	case tea.MouseWheelMsg:
		if !m.editing {
			util.HandleMouseWheel(msg, &m.bodyViewport)
		}

	case tea.KeyPressMsg:
		if m.editing {
			return m.updateEditMode(msg, &cmds)
		}
		return m.updateNormalMode(msg, &cmds)
	}

	return m, tea.Batch(cmds...)
}

func (m Model) updateNormalMode(msg tea.KeyPressMsg, cmds *[]tea.Cmd) (tea.Model, tea.Cmd) {
	onResponses := m.captureResponse && m.focusedPanel == panelResponses

	switch {
	case key.Matches(msg, keys.Keys.Global.Up):
		if onResponses {
			if m.responseCursor > 0 {
				m.responseCursor--
				m.refreshResponseListViewport()
				m.refreshBody()
			}
		} else {
			if m.cursor > 0 {
				m.cursor--
				m.refreshListViewport()
				m.refreshBody()
			}
		}

	case key.Matches(msg, keys.Keys.Global.Down):
		if onResponses {
			if m.responseCursor < len(m.responseQueue)-1 {
				m.responseCursor++
				m.refreshResponseListViewport()
				m.refreshBody()
			}
		} else {
			if m.cursor < len(m.queue)-1 {
				m.cursor++
				m.refreshListViewport()
				m.refreshBody()
			}
		}

	case key.Matches(msg, keys.Keys.Global.CycleFocus):
		if m.captureResponse {
			if m.focusedPanel == panelRequests {
				m.focusedPanel = panelResponses
			} else {
				m.focusedPanel = panelRequests
			}
			m.refreshBody()
		}

	case key.Matches(msg, keys.Keys.Global.ScrollUp):
		util.ScrollViewport(&m.bodyViewport, -1)

	case key.Matches(msg, keys.Keys.Global.ScrollDown):
		util.ScrollViewport(&m.bodyViewport, 1)

	case key.Matches(msg, keys.Keys.Global.Left):
		m.bodyViewport.ScrollLeft(6)

	case key.Matches(msg, keys.Keys.Global.Right):
		m.bodyViewport.ScrollRight(6)

	case key.Matches(msg, keys.Keys.Intercept.UndoEdits):
		if onResponses {
			if len(m.responseQueue) > 0 {
				delete(m.pendingResponseEdits, m.responseQueue[m.responseCursor])
				m.refreshBody()
			}
		} else {
			if len(m.queue) > 0 {
				delete(m.pendingEdits, m.queue[m.cursor])
				m.refreshBody()
			}
		}

	case key.Matches(msg, keys.Keys.Intercept.ToggleIntercept):
		m.interceptEnabled = !m.interceptEnabled
		if !m.interceptEnabled {
			for len(m.queue) > 0 {
				m.applyAndDecide(intercept.Forward)
			}
		}

	case key.Matches(msg, keys.Keys.Intercept.CaptureResponse):
		m.captureResponse = !m.captureResponse
		m.broker.SetCaptureResponse(m.captureResponse)
		if !m.captureResponse {
			for len(m.responseQueue) > 0 {
				m.broker.DecideResponse(m.responseQueue[0], intercept.Forward)
				m.responseQueue = m.responseQueue[1:]
			}
			m.responseCursor = 0
			m.focusedPanel = panelRequests
		}
		m.recalcSizes()

	case key.Matches(msg, keys.Keys.Global.Help):
		m.help.ShowAll = !m.help.ShowAll
		m.recalcSizes()

	case key.Matches(msg, keys.Keys.Intercept.Forward):
		if onResponses {
			m.applyAndDecideResponse(intercept.Forward)
		} else {
			m.applyAndDecide(intercept.Forward)
		}

	case key.Matches(msg, keys.Keys.Intercept.ForwardAll):
		if onResponses {
			for len(m.responseQueue) > 0 {
				m.applyAndDecideResponse(intercept.Forward)
			}
		} else {
			for len(m.queue) > 0 {
				m.applyAndDecide(intercept.Forward)
			}
		}

	case key.Matches(msg, keys.Keys.Intercept.Drop):
		if onResponses {
			m.applyAndDecideResponse(intercept.Drop)
		} else {
			m.applyAndDecide(intercept.Drop)
		}

	case key.Matches(msg, keys.Keys.Intercept.DropAll):
		if onResponses {
			for len(m.responseQueue) > 0 {
				m.applyAndDecideResponse(intercept.Drop)
			}
		} else {
			for len(m.queue) > 0 {
				m.applyAndDecide(intercept.Drop)
			}
		}

	case key.Matches(msg, keys.Keys.Intercept.Edit):
		hasItem := (!onResponses && len(m.queue) > 0) || (onResponses && len(m.responseQueue) > 0)
		if hasItem {
			raw := m.CurrentRaw()
			if len(raw) > maxInlineEditBytes {
				return m, util.OpenExternalEditor(raw)
			}
			m.loadIntoTextarea()
			m.editing = true
			m.textarea.Focus()
		}

	case key.Matches(msg, keys.Keys.Intercept.EditExternal):
		if !onResponses && len(m.queue) > 0 {
			return m, util.OpenExternalEditor(intercept.FormatRawRequest(m.queue[m.cursor].Flow))
		}
		if onResponses && len(m.responseQueue) > 0 {
			return m, util.OpenExternalEditor(intercept.FormatRawResponse(m.responseQueue[m.responseCursor].Flow))
		}

	case key.Matches(msg, keys.Keys.Global.SendToReplay):
		if !onResponses && len(m.queue) > 0 {
			req := m.queue[m.cursor]
			raw := m.CurrentRaw()
			scheme := req.Flow.Request.URL.Scheme
			if scheme == "" {
				scheme = "https"
			}
			return m, func() tea.Msg {
				return replayUI.SendToReplayMsg{
					Scheme:     scheme,
					Host:       req.Flow.Request.URL.Host,
					RequestRaw: raw,
				}
			}
		}

	case key.Matches(msg, keys.Keys.Global.SendToDiff):
		raw := m.CurrentRaw()
		if raw != "" {
			label := m.currentLabel()
			return m, func() tea.Msg {
				return diffUI.SendToDiffMsg{Label: label, Raw: raw}
			}
		}

	case key.Matches(msg, keys.Keys.Global.GotoTop):
		if onResponses {
			m.responseCursor = 0
		} else {
			m.cursor = 0
		}
		m.refreshListViewport()
		m.refreshResponseListViewport()
		m.refreshBody()

	case key.Matches(msg, keys.Keys.Global.GotoBottom):
		if onResponses {
			m.responseCursor = util.CursorGotoBottom(len(m.responseQueue))
		} else {
			m.cursor = util.CursorGotoBottom(len(m.queue))
		}
		m.refreshListViewport()
		m.refreshResponseListViewport()
		m.refreshBody()

	case key.Matches(msg, keys.Keys.Global.PrevPage):
		if onResponses {
			m.responseCursor = util.CursorMovePage(m.responseCursor, len(m.responseQueue), m.responsePager.PerPage, false)
		} else {
			m.cursor = util.CursorMovePage(m.cursor, len(m.queue), m.pager.PerPage, false)
		}
		m.refreshListViewport()
		m.refreshResponseListViewport()
		m.refreshBody()

	case key.Matches(msg, keys.Keys.Global.NextPage):
		if onResponses {
			m.responseCursor = util.CursorMovePage(m.responseCursor, len(m.responseQueue), m.responsePager.PerPage, true)
		} else {
			m.cursor = util.CursorMovePage(m.cursor, len(m.queue), m.pager.PerPage, true)
		}
		m.refreshListViewport()
		m.refreshResponseListViewport()
		m.refreshBody()
	}

	return m, tea.Batch(*cmds...)
}

func (m Model) updateEditMode(msg tea.KeyPressMsg, cmds *[]tea.Cmd) (tea.Model, tea.Cmd) {
	switch {
	case key.Matches(msg, keys.Keys.Global.Escape):
		m.saveCurrentEdit()
		m.editing = false
		m.textarea.Blur()
		m.refreshBodyViewport()

	default:
		var cmd tea.Cmd
		m.textarea, cmd = m.textarea.Update(msg)
		*cmds = append(*cmds, cmd)
	}

	return m, tea.Batch(*cmds...)
}
