package main

import (
	"fmt"
	"html/template"
	"net/http"
	"time"
)

var textSearchEngines []SearchEngine

func init() {
	textSearchEngines = []SearchEngine{
		{Name: "Google", Func: wrapTextSearchFunc(PerformGoogleTextSearch), Weight: 1},
		{Name: "LibreX", Func: wrapTextSearchFunc(PerformLibreXTextSearch), Weight: 2},
		{Name: "Brave", Func: wrapTextSearchFunc(PerformBraveTextSearch), Weight: 2},
		{Name: "DuckDuckGo", Func: wrapTextSearchFunc(PerformDuckDuckGoTextSearch), Weight: 5},
		// {Name: "SearXNG", Func: wrapTextSearchFunc(PerformSearXNGTextSearch), Weight: 2}, // Uncomment when implemented
	}
}

func HandleTextSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
	startTime := time.Now()

	cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "active", Lang: settings.SearchLanguage, Type: "text"}
	combinedResults := getTextResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.SearchLanguage, page)

	hasPrevPage := page > 1 // dupe

	//displayResults(w, combinedResults, query, lang, time.Since(startTime).Seconds(), page, hasPrevPage, hasNextPage)

	// Prefetch next and previous pages
	go prefetchPage(query, settings.SafeSearch, settings.SearchLanguage, page+1)
	if hasPrevPage {
		go prefetchPage(query, settings.SafeSearch, settings.SearchLanguage, page-1)
	}

	elapsedTime := time.Since(startTime)
	tmpl, err := template.New("text.html").Funcs(funcs).ParseFiles("templates/text.html")
	if err != nil {
		printErr("Error parsing template: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	data := struct {
		Results         []TextSearchResult
		Query           string
		Page            int
		Fetched         string
		HasPrevPage     bool
		HasNextPage     bool
		NoResults       bool
		LanguageOptions []LanguageOption
		CurrentLang     string
		Theme           string
		Safe            string
		IsThemeDark     bool
	}{
		Results:         combinedResults,
		Query:           query,
		Page:            page,
		Fetched:         fmt.Sprintf("%.2f seconds", elapsedTime.Seconds()),
		HasPrevPage:     page > 1,
		HasNextPage:     len(combinedResults) >= 50,
		NoResults:       len(combinedResults) == 0,
		LanguageOptions: languageOptions,
		CurrentLang:     settings.SearchLanguage,
		Theme:           settings.Theme,
		Safe:            settings.SafeSearch,
		IsThemeDark:     settings.IsThemeDark,
	}

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

func getTextResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []TextSearchResult {
	cacheChan := make(chan []SearchResult)
	var combinedResults []TextSearchResult

	go func() {
		results, exists := resultsCache.Get(cacheKey)
		if exists {
			printInfo("Cache hit")
			cacheChan <- results
		} else {
			printInfo("Cache miss")
			cacheChan <- nil
		}
	}()

	select {
	case results := <-cacheChan:
		if results == nil {
			combinedResults = fetchTextResults(query, safe, lang, page)
			if len(combinedResults) > 0 {
				resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
			}
		} else {
			textResults, _, _ := convertToSpecificResults(results)
			combinedResults = textResults
		}
	case <-time.After(2 * time.Second):
		printInfo("Cache check timeout")
		combinedResults = fetchTextResults(query, safe, lang, page)
		if len(combinedResults) > 0 {
			resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
		}
	}

	return combinedResults
}

func prefetchPage(query, safe, lang string, page int) {
	cacheKey := CacheKey{Query: query, Page: page, Safe: safe == "active", Lang: lang, Type: "text"}
	if _, exists := resultsCache.Get(cacheKey); !exists {
		printInfo("Page %d not cached, caching now...", page)
		pageResults := fetchTextResults(query, safe, lang, page)
		if len(pageResults) > 0 {
			resultsCache.Set(cacheKey, convertToSearchResults(pageResults))
		}
	} else {
		printInfo("Page %d already cached", page)
	}
}

func fetchTextResults(query, safe, lang string, page int) []TextSearchResult {
	var results []TextSearchResult

	for _, engine := range textSearchEngines {
		printInfo("Using search engine: %s", engine.Name)

		searchResults, duration, err := engine.Func(query, safe, lang, page)
		updateEngineMetrics(&engine, duration, err == nil)
		if err != nil {
			printWarn("Error performing search with %s: %v", engine.Name, err)
			continue
		}

		results = append(results, validateResults(searchResults)...)

		// If results are found, break out of the loop
		if len(results) > 0 {
			break
		}
	}

	// If no results found after trying all engines
	if len(results) == 0 {
		printWarn("No text results found for query: %s, trying other nodes", query)
		results = tryOtherNodesForTextSearch(query, safe, lang, page, []string{hostID})
	}

	return results
}

func validateResults(searchResults []SearchResult) []TextSearchResult {
	var validResults []TextSearchResult

	// Remove anything that is missing a URL or Header
	for _, result := range searchResults {
		textResult := result.(TextSearchResult)
		if textResult.URL != "" || textResult.Header != "" {
			validResults = append(validResults, textResult)
		}
	}

	return validResults
}

func wrapTextSearchFunc(f func(string, string, string, int) ([]TextSearchResult, time.Duration, error)) func(string, string, string, int) ([]SearchResult, time.Duration, error) {
	return func(query, safe, lang string, page int) ([]SearchResult, time.Duration, error) {
		textResults, duration, err := f(query, safe, lang, page)
		if err != nil {
			return nil, duration, err
		}
		searchResults := make([]SearchResult, len(textResults))
		for i, result := range textResults {
			searchResults[i] = result
		}
		return searchResults, duration, nil
	}
}