package main

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"html/template"
	mathrand "math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"
)

var (
	funcs = template.FuncMap{
		"sub": func(a, b int) int {
			return a - b
		},
		"add": func(a, b int) int {
			return a + b
		},
		"translate": Translate,
		"toJSON": func(v interface{}) (string, error) {
			jsonBytes, err := json.Marshal(v)
			if err != nil {
				return "", err
			}
			return string(jsonBytes), nil
		},
	}
)

type SearchEngine struct {
	Name string
	Func func(string, string, string, int) ([]SearchResult, time.Duration, error)
}

type LinkParts struct {
	Domain  template.HTML
	Path    template.HTML
	RootURL string // used by getFaviconProxyURL()
}

// Helper function to render templates without elapsed time measurement
func renderTemplate(w http.ResponseWriter, tmplName string, data map[string]interface{}) {
	// Generate icon paths for SVG and PNG, including a 1/10 chance for an alternate icon
	iconPathSVG, iconPathPNG := GetIconPath()

	// Add icon paths to data map so they are available in all templates
	if data == nil {
		data = make(map[string]interface{})
	}
	data["IconPathSVG"] = iconPathSVG
	data["IconPathPNG"] = iconPathPNG

	// Parse and execute the template with shared functions
	tmpl, err := template.New(tmplName).Funcs(funcs).ParseFiles("templates/" + tmplName)
	if err != nil {
		printErr("Error parsing template: %v", err)
		http.Error(w, Translate("internal_server_error"), http.StatusInternalServerError)
		return
	}

	// Execute the template
	err = tmpl.Execute(w, data)
	if err != nil {
		printErr("Error executing template: %v", err)
		http.Error(w, Translate("internal_server_error"), http.StatusInternalServerError)
	}
}

// Randoms string generator used for auth code
func generateStrongRandomString(length int) string {
	bytes := make([]byte, length)
	_, err := rand.Read(bytes)
	if err != nil {
		printErr("Error generating random string: %v", err)
	}
	return base64.URLEncoding.EncodeToString(bytes)[:length]
}

// Checks if the URL already includes a protocol
func hasProtocol(url string) bool {
	return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")
}

// Checks if the domain is a local address
func isLocalAddress(domain string) bool {
	return domain == "localhost" || strings.HasPrefix(domain, "127.") || strings.HasPrefix(domain, "192.168.") || strings.HasPrefix(domain, "10.")
}

// Ensures that HTTP or HTTPS is before the address if needed
func addProtocol(domain string) string {
	if hasProtocol(domain) {
		return domain
	}
	if isLocalAddress(domain) {
		return "http://" + domain
	}
	return "https://" + domain
}

// GetIconPath returns both SVG and PNG icon paths, with a 1/10 chance for a randomly generated "alt" icon.
func GetIconPath() (string, string) {
	// 1 in 10 chance to select an alt icon
	if mathrand.Intn(10) == 0 {
		// Generate a random number between 2 and 4
		altIconNumber := 2 + mathrand.Intn(3) // mathrand.Intn(3) generates 0, 1, or 2
		selectedAltIcon := "icon-alt" + fmt.Sprint(altIconNumber)
		return "/static/images/" + selectedAltIcon + ".svg", "/static/images/" + selectedAltIcon + ".png"
	}
	// Default paths
	return "/static/images/icon.svg", "/static/images/icon.png"
}

// FormatElapsedTime formats elapsed time as a string,
// using:
// - "> 0.01 ms" if under 49µs
// - "0.xx ms" if under 1ms
// - "xxx ms" if under 300ms
// - "x.xx seconds" otherwise
func FormatElapsedTime(elapsed time.Duration) string {
	if elapsed < 49*time.Microsecond {
		return fmt.Sprintf("> 0.01 %s", Translate("milliseconds"))
	} else if elapsed < time.Millisecond {
		ms := float64(elapsed.Microseconds()) / 1000.0
		return fmt.Sprintf("%.2f %s", ms, Translate("milliseconds"))
	} else if elapsed < 300*time.Millisecond {
		return fmt.Sprintf("%d %s", elapsed.Milliseconds(), Translate("milliseconds"))
	}
	return fmt.Sprintf("%.2f %s", elapsed.Seconds(), Translate("seconds"))
}
func FormatURLParts(rawURL string) (domain, path, rootURL string) {
	parsed, err := url.Parse(rawURL)
	if err != nil || parsed.Host == "" {
		return "", "", ""
	}

	domain = parsed.Host
	if strings.HasPrefix(domain, "www.") {
		domain = domain[4:]
	}

	rootURL = parsed.Scheme + "://" + parsed.Host

	path = strings.Trim(parsed.Path, "/")
	pathSegments := strings.Split(path, "/")
	var cleanSegments []string
	for _, seg := range pathSegments {
		if seg != "" {
			cleanSegments = append(cleanSegments, seg)
		}
	}
	path = strings.Join(cleanSegments, "/")
	return domain, path, rootURL
}

func FormatLinkHTML(rawURL string) LinkParts {
	domain, path, root := FormatURLParts(rawURL)

	lp := LinkParts{
		RootURL: root,
	}

	lp.Domain = template.HTML(fmt.Sprintf(`<span class="result-domain">%s</span>`, template.HTMLEscapeString(domain)))

	if path != "" {
		pathDisplay := strings.ReplaceAll(path, "/", " › ")
		lp.Path = template.HTML(fmt.Sprintf(`<span class="result-path"> › %s</span>`, template.HTMLEscapeString(pathDisplay)))
	}

	return lp
}

// Converts any struct to a map[string]interface{} using JSON round-trip.
// Useful for rendering templates with generic map input.
func toMap(data interface{}) map[string]interface{} {
	jsonBytes, _ := json.Marshal(data)
	var result map[string]interface{}
	_ = json.Unmarshal(jsonBytes, &result)
	return result
}