package intercept

import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	ilovetui "github.com/anotherhadi/ilovetui"
	"github.com/anotherhadi/spilltea/internal/intercept"
	"github.com/anotherhadi/spilltea/internal/style"
	"github.com/anotherhadi/spilltea/internal/util"
)

func parseRawRequest(content string, req *intercept.PendingRequest) {
	parsed := util.ParseRawRequest(content)
	r := req.Flow.Request
	if parsed.Method != "" {
		r.Method = parsed.Method
	}
	if parsed.Path != "" {
		if u, err := url.ParseRequestURI(parsed.Path); err == nil {
			r.URL.Path = u.Path
			r.URL.RawQuery = u.RawQuery
		}
	}
	if parsed.Proto != "" {
		r.Proto = parsed.Proto
	}
	if parsed.Host != "" {
		r.URL.Host = parsed.Host
	}
	r.Header = make(http.Header)
	for _, h := range parsed.Headers {
		if strings.EqualFold(h.Key, "host") {
			continue
		}
		r.Header.Set(h.Key, h.Value)
	}
	if parsed.Body != "" {
		r.Body = []byte(parsed.Body)
	} else {
		r.Body = nil
	}
}

func parseRawResponse(content string, resp *intercept.PendingResponse) {
	r := resp.Flow.Response
	if r == nil {
		return
	}
	lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n")
	if len(lines) == 0 {
		return
	}

	parts := strings.SplitN(lines[0], " ", 3)
	if len(parts) >= 2 {
		if code, err := strconv.Atoi(strings.TrimSpace(parts[1])); err == nil {
			r.StatusCode = code
		}
	}

	r.Header = make(http.Header)
	i := 1
	for i < len(lines) {
		line := strings.TrimRight(lines[i], "\r")
		if line == "" {
			i++
			break
		}
		if kv := strings.SplitN(line, ": ", 2); len(kv) == 2 {
			r.Header.Set(strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]))
		}
		i++
	}

	if i < len(lines) {
		body := strings.Join(lines[i:], "\n")
		body = strings.TrimRight(body, "\n")
		if body != "" {
			r.Body = []byte(body)
		} else {
			r.Body = nil
		}
	}
	r.Header.Set("Content-Length", strconv.Itoa(len(r.Body)))
}

func (m *Model) currentLabel() string {
	if m.captureResponse && m.focusedPanel == panelResponses {
		if len(m.responseQueue) == 0 {
			return ""
		}
		resp := m.responseQueue[m.responseCursor]
		code := 0
		if resp.Flow.Response != nil {
			code = resp.Flow.Response.StatusCode
		}
		return fmt.Sprintf("%d %s %s", code, http.StatusText(code), resp.Flow.Request.URL.RequestURI())
	}
	if len(m.queue) == 0 {
		return ""
	}
	req := m.queue[m.cursor]
	return req.Flow.Request.Method + " " + req.Flow.Request.URL.RequestURI()
}

func (m *Model) removeFromQueue(index int) {
	m.queue = append(m.queue[:index], m.queue[index+1:]...)
	if m.cursor >= len(m.queue) && m.cursor > 0 {
		m.cursor--
	}
	m.refreshListViewport()
	m.refreshBody()
}

func (m *Model) removeFromResponseQueue(index int) {
	m.responseQueue = append(m.responseQueue[:index], m.responseQueue[index+1:]...)
	if m.responseCursor >= len(m.responseQueue) && m.responseCursor > 0 {
		m.responseCursor--
	}
	m.refreshResponseListViewport()
	m.refreshBody()
}

func (m *Model) applyAndDecide(d intercept.Decision) {
	if len(m.queue) == 0 {
		return
	}
	req := m.queue[m.cursor]
	if d == intercept.Forward {
		if edited, ok := m.pendingEdits[req]; ok {
			parseRawRequest(edited, req)
		}
	}
	delete(m.pendingEdits, req)
	m.broker.Decide(req, d)
	m.removeFromQueue(m.cursor)
}

func (m *Model) applyAndDecideResponse(d intercept.Decision) {
	if len(m.responseQueue) == 0 {
		return
	}
	resp := m.responseQueue[m.responseCursor]
	if d == intercept.Forward {
		if edited, ok := m.pendingResponseEdits[resp]; ok {
			parseRawResponse(edited, resp)
		}
	}
	delete(m.pendingResponseEdits, resp)
	m.broker.DecideResponse(resp, d)
	m.removeFromResponseQueue(m.responseCursor)
}

func (m *Model) listHalfWidths() (leftW, rightW int) {
	leftW = m.width / 2
	rightW = m.width - leftW
	return
}

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

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

	bodyInner := m.width - 2
	if bodyInner < 0 {
		bodyInner = 0
	}
	bodyVH := ilovetui.ContentHeight(bodyH)

	m.textarea.SetWidth(bodyInner)
	m.textarea.SetHeight(bodyVH)
	m.bodyViewport.SetWidth(bodyInner)
	m.bodyViewport.SetHeight(bodyVH)

	listVH := ilovetui.ContentHeight(listH) - 1 // -1 for the pager dots row
	if listVH < 0 {
		listVH = 0
	}

	if m.captureResponse {
		leftW, rightW := m.listHalfWidths()
		leftInner := leftW - 2
		rightInner := rightW - 2
		if leftInner < 0 {
			leftInner = 0
		}
		if rightInner < 0 {
			rightInner = 0
		}

		m.listViewport.SetWidth(leftInner)
		m.listViewport.SetHeight(listVH)
		m.pager.PerPage = listVH
		if m.pager.PerPage < 1 {
			m.pager.PerPage = 1
		}

		m.responseViewport.SetWidth(rightInner)
		m.responseViewport.SetHeight(listVH)
		m.responsePager.PerPage = listVH
		if m.responsePager.PerPage < 1 {
			m.responsePager.PerPage = 1
		}
	} else {
		listInner := m.width - 2
		if listInner < 0 {
			listInner = 0
		}

		m.listViewport.SetWidth(listInner)
		m.listViewport.SetHeight(listVH)
		m.pager.PerPage = listVH
		if m.pager.PerPage < 1 {
			m.pager.PerPage = 1
		}
	}

	m.refreshListViewport()
	m.refreshResponseListViewport()
	m.refreshBody()
}

func (m *Model) refreshListViewport() {
	if m.pager.PerPage > 0 {
		if len(m.queue) == 0 {
			m.pager.Page = 0
			m.pager.TotalPages = 0
		} else {
			m.pager.Page = m.cursor / m.pager.PerPage
			m.pager.SetTotalPages(len(m.queue))
		}
	}
	m.listViewport.SetContent(m.renderList())
}

func (m *Model) refreshResponseListViewport() {
	if m.responsePager.PerPage > 0 {
		if len(m.responseQueue) == 0 {
			m.responsePager.Page = 0
			m.responsePager.TotalPages = 0
		} else {
			m.responsePager.Page = m.responseCursor / m.responsePager.PerPage
			m.responsePager.SetTotalPages(len(m.responseQueue))
		}
	}
	m.responseViewport.SetContent(m.renderResponseList())
}

// saveCurrentEdit must only be called when exiting edit mode.
func (m *Model) saveCurrentEdit() {
	if m.captureResponse && m.focusedPanel == panelResponses {
		if len(m.responseQueue) > 0 {
			m.pendingResponseEdits[m.responseQueue[m.responseCursor]] = m.textarea.Value()
		}
	} else {
		if len(m.queue) > 0 {
			m.pendingEdits[m.queue[m.cursor]] = m.textarea.Value()
		}
	}
}

const maxInlineEditBytes = 32 * 1024

func (m *Model) loadIntoTextarea() {
	if m.captureResponse && m.focusedPanel == panelResponses {
		if len(m.responseQueue) == 0 {
			return
		}
		resp := m.responseQueue[m.responseCursor]
		if edited, ok := m.pendingResponseEdits[resp]; ok {
			m.textarea.SetValue(edited)
		} else {
			m.textarea.SetValue(intercept.FormatRawResponse(resp.Flow))
		}
	} else {
		if len(m.queue) == 0 {
			return
		}
		req := m.queue[m.cursor]
		if edited, ok := m.pendingEdits[req]; ok {
			m.textarea.SetValue(edited)
		} else {
			m.textarea.SetValue(intercept.FormatRawRequest(req.Flow))
		}
	}
}

// refreshBody does not touch the textarea - it is only loaded when entering edit mode.
func (m *Model) refreshBody() {
	var raw string
	if m.captureResponse && m.focusedPanel == panelResponses {
		if len(m.responseQueue) == 0 {
			m.bodyViewport.SetContent("")
			return
		}
		resp := m.responseQueue[m.responseCursor]
		if edited, ok := m.pendingResponseEdits[resp]; ok {
			raw = edited
		} else {
			raw = intercept.FormatRawResponse(resp.Flow)
		}
	} else {
		if len(m.queue) == 0 {
			m.bodyViewport.SetContent("")
			return
		}
		req := m.queue[m.cursor]
		if edited, ok := m.pendingEdits[req]; ok {
			raw = edited
		} else {
			raw = intercept.FormatRawRequest(req.Flow)
		}
	}
	m.bodyViewport.SetContent(style.HighlightHTTP(raw))
	m.bodyViewport.SetYOffset(0)
	m.bodyViewport.SetXOffset(0)
}

func (m *Model) refreshBodyViewport() {
	m.bodyViewport.SetContent(style.HighlightHTTP(m.textarea.Value()))
}
