package search

import (
	"errors"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

var languageCodeRe = regexp.MustCompile(`^[a-z]{2,3}(-[a-zA-Z]{2})?$`)

func ParseSearchRequest(r *http.Request) (SearchRequest, error) {
	// SearXNG supports both GET and POST and relies on form values for routing.
	if err := r.ParseForm(); err != nil {
		return SearchRequest{}, errors.New("invalid request: cannot parse form")
	}

	format := strings.ToLower(r.FormValue("format"))
	switch OutputFormat(format) {
	case FormatJSON, FormatCSV, FormatRSS:
	default:
		// MVP: treat everything else as json, except `html` which we accept for compatibility.
		if format == string(FormatHTML) {
			// accepted, but not implemented by the server yet
		} else {
			format = string(FormatJSON)
		}
	}

	q := r.FormValue("q")
	if strings.TrimSpace(q) == "" {
		return SearchRequest{}, errors.New("missing required parameter: q")
	}

	pageno := 1
	if s := strings.TrimSpace(r.FormValue("pageno")); s != "" {
		n, err := strconv.Atoi(s)
		if err != nil || n < 1 {
			return SearchRequest{}, errors.New("invalid parameter: pageno")
		}
		pageno = n
	}

	// MVP defaults.
	safesearch := 0
	if s := strings.TrimSpace(r.FormValue("safesearch")); s != "" {
		n, err := strconv.Atoi(s)
		if err != nil || n < 0 || n > 2 {
			return SearchRequest{}, errors.New("invalid parameter: safesearch")
		}
		safesearch = n
	}

	var timeRange *string
	if tr := strings.TrimSpace(r.FormValue("time_range")); tr != "" && tr != "None" {
		switch tr {
		case "day", "week", "month", "year":
			tt := tr
			timeRange = &tt
		default:
			return SearchRequest{}, errors.New("invalid parameter: time_range")
		}
	}

	var timeoutLimit *float64
	if s := strings.TrimSpace(r.FormValue("timeout_limit")); s != "" && s != "None" {
		v, err := strconv.ParseFloat(s, 64)
		if err != nil || v <= 0 {
			return SearchRequest{}, errors.New("invalid parameter: timeout_limit")
		}
		timeoutLimit = &v
	}

	language := strings.TrimSpace(r.FormValue("language"))
	if language == "" {
		language = "auto"
	}
	switch language {
	case "auto", "all":
		// ok
	default:
		if !languageCodeRe.MatchString(language) {
			return SearchRequest{}, errors.New("invalid parameter: language")
		}
	}

	// engines is an explicit list of engine names.
	engines := splitCSV(strings.TrimSpace(r.FormValue("engines")))

	// categories and category_<name> params mirror SearXNG's webadapter parsing.
	// We don't validate against a registry here; we just preserve the requested values.
	catSet := map[string]bool{}
	if catsParam := strings.TrimSpace(r.FormValue("categories")); catsParam != "" {
		for _, cat := range splitCSV(catsParam) {
			catSet[cat] = true
		}
	}
	for k, v := range r.Form {
		if !strings.HasPrefix(k, "category_") {
			continue
		}
		category := strings.TrimPrefix(k, "category_")
		if category == "" {
			continue
		}
		val := ""
		if len(v) > 0 {
			val = strings.TrimSpace(v[0])
		}
		if val == "" || val != "off" {
			catSet[category] = true
		} else {
			delete(catSet, category)
		}
	}
	categories := make([]string, 0, len(catSet))
	for c := range catSet {
		categories = append(categories, c)
	}
	if len(categories) == 0 {
		categories = []string{"general"}
	}

	// Parse engine_data-<engine>-<key>=<value> parameters.
	engineData := map[string]map[string]string{}
	for k, v := range r.Form {
		if !strings.HasPrefix(k, "engine_data-") {
			continue
		}
		parts := strings.SplitN(k, "-", 3) // engine_data-<engine>-<key>
		if len(parts) != 3 {
			continue
		}
		engine := parts[1]
		key := parts[2]
		// For HTML forms, r.Form[k] can contain multiple values; keep first.
		val := ""
		if len(v) > 0 {
			val = v[0]
		}
		if _, ok := engineData[engine]; !ok {
			engineData[engine] = map[string]string{}
		}
		engineData[engine][key] = val
	}

	accessToken := parseAccessToken(r)

	return SearchRequest{
		Format:       OutputFormat(format),
		Query:       q,
		Pageno:      pageno,
		Safesearch:  safesearch,
		TimeRange:   timeRange,
		TimeoutLimit: timeoutLimit,
		Language:    language,
		Engines:     engines,
		Categories:  categories,
		EngineData:  engineData,
		AccessToken: accessToken,
	}, nil
}

func splitCSV(s string) []string {
	if s == "" {
		return nil
	}
	raw := strings.Split(s, ",")
	out := make([]string, 0, len(raw))
	for _, item := range raw {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		out = append(out, item)
	}
	return out
}

func parseAccessToken(r *http.Request) string {
	// Supported sources (first non-empty wins):
	// - `Authorization: Bearer <token>`
	// - `X-Search-Token` / `X-Brave-Access-Token`
	// - `token` form value
	if auth := r.Header.Get("Authorization"); auth != "" {
		const prefix = "Bearer "
		if len(auth) > len(prefix) && auth[:len(prefix)] == prefix {
			return strings.TrimSpace(auth[len(prefix):])
		}
	}

	if v := strings.TrimSpace(r.Header.Get("X-Search-Token")); v != "" {
		return v
	}
	if v := strings.TrimSpace(r.Header.Get("X-Brave-Access-Token")); v != "" {
		return v
	}

	if v := strings.TrimSpace(r.FormValue("token")); v != "" {
		return v
	}

	return ""
}

