package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strings"

	"github.com/robertjanetzko/LegendsBrowser2/backend/model"
	"github.com/robertjanetzko/LegendsBrowser2/backend/util"
)

type searchHandler struct {
	server *DfServer
}

type SearchResult struct {
	Label string `json:"label"`
	Value string `json:"value"`
}

func (h searchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if h.server.context.world == nil {
		h.server.renderLoading(w, r)
		return
	}

	term := r.URL.Query().Get("term")

	world := h.server.context.world
	if term != "" {
		var results []SearchResult
		results = searchMap(term, world.HistoricalFigures, results, "/hf")
		results = searchMap(term, world.Entities, results, "/entity")
		results = searchMap(term, world.Sites, results, "/site")
		for _, site := range world.Sites {
			results = searchMap(term, site.Structures, results, fmt.Sprintf("/site/%d/structure", site.Id_))
		}
		results = searchMap(term, world.Regions, results, "/region")
		results = searchMap(term, world.Artifacts, results, "/artifavt")
		results = searchMap(term, world.WorldConstructions, results, "/worldconstruction")
		results = searchMap(term, world.DanceForms, results, "/danceform")
		results = searchMap(term, world.MusicalForms, results, "/musicalform")
		results = searchMap(term, world.PoeticForms, results, "/poeticform")
		results = searchMap(term, world.WrittenContents, results, "/writtencontent")
		results = searchMap(term, world.Landmasses, results, "/landmass")
		results = searchMap(term, world.MountainPeaks, results, "/mountain")

		sort.Slice(results, func(i, j int) bool { return results[i].Label < results[j].Label })

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(results)
	} else {
		term = r.URL.Query().Get("search")

		var structures []*model.Structure
		for _, site := range world.Sites {
			structures = search(term, site.Structures, structures)
		}

		results := struct {
			Term               string
			HistoricalFigures  []*model.HistoricalFigure
			Entities           []*model.Entity
			Sites              []*model.Site
			Structures         []*model.Structure
			Regions            []*model.Region
			Artifacts          []*model.Artifact
			WorldConstructions []*model.WorldConstruction
			DanceForms         []*model.DanceForm
			MusicalForms       []*model.MusicalForm
			PoeticForms        []*model.PoeticForm
			WrittenContents    []*model.WrittenContent
			Landmasses         []*model.Landmass
			MountainPeaks      []*model.MountainPeak
		}{
			Term:               term,
			HistoricalFigures:  search(term, world.HistoricalFigures, nil),
			Entities:           search(term, world.Entities, nil),
			Sites:              search(term, world.Sites, nil),
			Structures:         structures,
			Regions:            search(term, world.Regions, nil),
			Artifacts:          search(term, world.Artifacts, nil),
			WorldConstructions: search(term, world.WorldConstructions, nil),
			DanceForms:         search(term, world.DanceForms, nil),
			MusicalForms:       search(term, world.MusicalForms, nil),
			PoeticForms:        search(term, world.PoeticForms, nil),
			WrittenContents:    search(term, world.WrittenContents, nil),
			Landmasses:         search(term, world.Landmasses, nil),
			MountainPeaks:      search(term, world.MountainPeaks, nil),
		}

		err := h.server.templates.Render(w, "search.html", results)
		if err != nil {
			httpError(w, err)
		}
	}
}

func searchMap[T model.Named](s string, input map[int]T, output []SearchResult, baseUrl string) []SearchResult {
	s = strings.ToLower(s)
	for id, v := range input {
		if strings.Contains(strings.ToLower(v.Name()), s) {
			output = append(output, SearchResult{
				Label: util.Title(v.Name()),
				Value: fmt.Sprintf("%s/%d", baseUrl, id),
			})
		}
	}
	return output
}

func search[T model.Named](s string, input map[int]T, output []T) []T {
	s = strings.ToLower(s)
	for _, v := range input {
		if strings.Contains(strings.ToLower(v.Name()), s) {
			output = append(output, v)
		}
	}
	sort.Slice(output, func(i, j int) bool { return output[i].Name() < output[j].Name() })
	return output
}
