package replay

import (
	"fmt"
	"strings"

	"charm.land/bubbles/v2/help"
	"charm.land/bubbles/v2/key"
	"charm.land/bubbles/v2/paginator"
	"charm.land/bubbles/v2/textarea"
	"charm.land/bubbles/v2/textinput"
	"charm.land/bubbles/v2/viewport"
	tea "charm.land/bubbletea/v2"
	ilovetui "github.com/anotherhadi/ilovetui"
	"github.com/anotherhadi/spilltea/internal/db"
	"github.com/anotherhadi/spilltea/internal/keys"
)

type SendToReplayMsg struct {
	Scheme     string
	Host       string
	RequestRaw string
}

type Entry struct {
	DBID        int64
	Scheme      string
	Host        string
	Path        string
	Method      string
	OriginalRaw string
	RequestRaw  string // current (possibly edited) request
	ResponseRaw string // filled after send
	StatusCode  int    // 0 = not sent yet
	Sending     bool
	Flagged     bool
	Err         error
}

type panel int

const (
	panelList panel = iota
	panelRequest
	panelResponse
)

type Model struct {
	allEntries   []Entry
	entries      []Entry // filtered view; equals allEntries when no filter
	entryIndices []int   // maps entries[i] -> allEntries[i]; nil when no filter
	cursor       int
	editing      bool
	focusedPanel panel
	database     *db.DB

	filterInput    textinput.Model
	filterActive   bool
	filterAccepted bool

	listViewport     viewport.Model
	requestViewport  viewport.Model
	responseViewport viewport.Model
	textarea         textarea.Model
	pager            paginator.Model
	help             help.Model

	width  int
	height int
}

func New() Model {
	ta := ilovetui.NewTextarea(false)
	ta.Blur()
	ti := textinput.New()
	ti.Prompt = ""
	return Model{
		listViewport:     ilovetui.NewViewport(),
		requestViewport:  ilovetui.NewViewport(),
		responseViewport: ilovetui.NewViewport(),
		textarea:         ta,
		filterInput:      ti,
		pager:            ilovetui.NewPaginator(),
		help:             ilovetui.NewHelp(),
	}
}

func (m Model) Init() tea.Cmd { return nil }

func (m Model) IsEditing() bool { return m.editing || (m.filterActive && !m.filterAccepted) }

func (m Model) IsResponseFocused() bool {
	return m.focusedPanel == panelResponse
}

func (m Model) CurrentRaw() string {
	if len(m.entries) == 0 || m.cursor >= len(m.entries) {
		return ""
	}
	if m.focusedPanel == panelResponse {
		return m.entries[m.cursor].ResponseRaw
	}
	return m.entries[m.cursor].RequestRaw
}

func (m Model) CurrentScheme() string {
	if len(m.entries) == 0 || m.cursor >= len(m.entries) {
		return "https"
	}
	if s := m.entries[m.cursor].Scheme; s != "" {
		return s
	}
	return "https"
}

func (m *Model) SetDB(d *db.DB) {
	m.database = d
	if d == nil {
		return
	}
	entries, err := d.ListReplayEntries()
	if err != nil {
		return
	}
	for _, dbe := range entries {
		m.allEntries = append(m.allEntries, entryFromDB(dbe))
	}
	m.entries = m.allEntries
	m.pager.SetTotalPages(len(m.entries))
	if len(m.entries) > 0 {
		m.cursor = len(m.entries) - 1
	}
	m.refreshListViewport()
	m.refreshBody()
}

func entryFromDB(dbe db.ReplayEntry) Entry {
	var err error
	if dbe.ErrorMsg != "" {
		err = fmt.Errorf("%s", dbe.ErrorMsg)
	}
	return Entry{
		DBID:        dbe.ID,
		Scheme:      dbe.Scheme,
		Host:        dbe.Host,
		Path:        dbe.Path,
		Method:      dbe.Method,
		OriginalRaw: dbe.OriginalRaw,
		RequestRaw:  dbe.RequestRaw,
		ResponseRaw: dbe.ResponseRaw,
		StatusCode:  dbe.StatusCode,
		Flagged:     dbe.Flagged,
		Err:         err,
	}
}

func (m *Model) SetSize(w, h int) {
	m.width = w
	m.height = h
	m.recalcSizes()
}

func (m *Model) recalcSizes() {
	m.help.SetWidth(m.width - 2)
	m.filterInput.SetWidth(m.width - 4)

	listH, bodyH := ilovetui.SplitH(m.height, m.renderStatusBar(), 0.35)

	listInner := m.width - 2
	if listInner < 0 {
		listInner = 0
	}
	listVH := ilovetui.ContentHeight(listH) - 1 // -1 for the pager dots row
	if listVH < 0 {
		listVH = 0
	}
	m.listViewport.SetWidth(listInner)
	m.listViewport.SetHeight(listVH)
	m.pager.PerPage = listVH
	if m.pager.PerPage < 1 {
		m.pager.PerPage = 1
	}

	leftW, rightW := m.bodyHalfWidths()
	leftInner := leftW - 2
	rightInner := rightW - 2
	if leftInner < 0 {
		leftInner = 0
	}
	if rightInner < 0 {
		rightInner = 0
	}
	bodyVH := ilovetui.ContentHeight(bodyH)

	m.requestViewport.SetWidth(leftInner)
	m.requestViewport.SetHeight(bodyVH)
	m.responseViewport.SetWidth(rightInner)
	m.responseViewport.SetHeight(bodyVH)
	m.textarea.SetWidth(leftInner)
	m.textarea.SetHeight(bodyVH)

	m.refreshListViewport()
	m.refreshBody()
}

func (m *Model) bodyHalfWidths() (left, right int) {
	left = m.width / 2
	right = m.width - left
	return
}

func (m *Model) currentAllIdx() int {
	if m.entryIndices != nil && m.cursor < len(m.entryIndices) {
		return m.entryIndices[m.cursor]
	}
	if m.cursor < len(m.allEntries) {
		return m.cursor
	}
	return -1
}

func matchesFilter(e Entry, term string) bool {
	return strings.Contains(strings.ToLower(e.Host), term) ||
		strings.Contains(strings.ToLower(e.Path), term) ||
		strings.Contains(strings.ToLower(strings.TrimSpace(e.Method)), term)
}

func (m *Model) applyFilter(preferAllIdx int) {
	term := strings.ToLower(m.filterInput.Value())
	if !m.filterActive || term == "" {
		m.entries = m.allEntries
		m.entryIndices = nil
		if preferAllIdx >= 0 && preferAllIdx < len(m.allEntries) {
			m.cursor = preferAllIdx
		}
	} else {
		m.entries = nil
		m.entryIndices = nil
		newCursor := 0
		found := false
		for i, e := range m.allEntries {
			if matchesFilter(e, term) {
				if i == preferAllIdx && !found {
					newCursor = len(m.entries)
					found = true
				}
				m.entries = append(m.entries, e)
				m.entryIndices = append(m.entryIndices, i)
			}
		}
		if found {
			m.cursor = newCursor
		} else {
			m.cursor = 0
		}
	}
	if len(m.entries) == 0 {
		m.cursor = 0
	} else if m.cursor >= len(m.entries) {
		m.cursor = len(m.entries) - 1
	}
	m.pager.SetTotalPages(len(m.entries))
	m.refreshListViewport()
	m.refreshBody()
}

func (m *Model) clearFilter() {
	prevAllIdx := m.currentAllIdx()
	m.filterActive = false
	m.filterAccepted = false
	m.filterInput.SetValue("")
	m.filterInput.Blur()
	m.recalcSizes()
	m.applyFilter(prevAllIdx)
}

type replayKeyMap struct{ width int }

func (replayKeyMap) ShortHelp() []key.Binding {
	g := keys.Keys.Global
	r := keys.Keys.Replay
	return []key.Binding{g.Up, g.Down, g.CycleFocus, r.Send, r.Edit, r.Flag, r.Filter, g.Help}
}

func (m replayKeyMap) FullHelp() [][]key.Binding {
	g := keys.Keys.Global
	pageGlobals := []key.Binding{g.Up, g.Down, g.CycleFocus, g.ScrollUp, g.ScrollDown, g.Left, g.Right, g.Escape, g.Copy, g.CopyAs, g.SendToDiff}
	all := append(keys.Keys.Replay.Bindings(), pageGlobals...)
	all = append(all, g.CommonBindings()...)
	return keys.ChunkByWidth(all, m.width)
}
