package viewmodels

import (
	"fmt"
	"strings"
	"sync"

	"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/datatypes"
	searchprovider "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/search"
	"github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels"
	"github.com/blevesearch/bleve/v2"
	"github.com/blevesearch/bleve/v2/search/query"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
	"golang.org/x/text/unicode/norm"
)

type Result[T any] struct {
	Count uint64
	Items []T
}

type SearchView struct {
	Agents     Result[xmlmodels.Agent]
	Works      Result[xmlmodels.Work]
	Places     Result[xmlmodels.Place]
	Categories Result[xmlmodels.Category]
	Pieces     Result[xmlmodels.Piece]
	Issues     Result[xmlmodels.Issue]
}

func NewSearchView(search string, kgpz *xmlmodels.Library, sp *searchprovider.SearchProvider) (*SearchView, error) {
	sw := SearchView{}
	search = datatypes.DeleteTags(search)
	search = datatypes.NormalizeString(search)
	search = datatypes.RemovePunctuation(search)
	search = cases.Lower(language.German).String(search)
	search = norm.NFKD.String(search)

	searches := strings.Split(search, " ")
	queries := make([]query.Query, 0, len(searches))
	for _, s := range searches {
		if strings.TrimSpace(s) == "" {
			continue
		}
		queries = append(queries, bleve.NewTermQuery(s))
	}

	query := bleve.NewConjunctionQuery(queries...)
	request := bleve.NewSearchRequest(query)
	request.Size = 100

	agentIndex, erragent := sp.GetIndex(xmlmodels.AGENT_TYPE)
	workIndex, errwork := sp.GetIndex(xmlmodels.WORK_TYPE)
	placeIndex, errplace := sp.GetIndex(xmlmodels.PLACE_TYPE)
	categoryIndex, errcategory := sp.GetIndex(xmlmodels.CATEGORY_TYPE)
	pieceIndex, errpiece := sp.GetIndex(xmlmodels.PIECE_TYPE)
	issueIndex, errissue := sp.GetIndex(xmlmodels.ISSUE_TYPE)

	if agentIndex == nil || workIndex == nil || placeIndex == nil || categoryIndex == nil || pieceIndex == nil || issueIndex == nil {
		return nil, fmt.Errorf("Indeces not found.")
	}

	wg := sync.WaitGroup{}
	if erragent == nil {
		wg.Add(1)
		go func() {
			agentResults, _ := agentIndex.Search(request)
			result := Result[xmlmodels.Agent]{Count: agentResults.Total}
			for _, hit := range agentResults.Hits {
				agent := kgpz.Agents.Item(hit.ID)
				if agent != nil {
					result.Items = append(result.Items, *agent)
				}
			}
			sw.Agents = result
			wg.Done()
		}()
	}

	if errwork == nil {
		wg.Add(1)
		go func() {
			workResults, _ := workIndex.Search(request)
			result := Result[xmlmodels.Work]{Count: workResults.Total}
			for _, hit := range workResults.Hits {
				work := kgpz.Works.Item(hit.ID)
				if work != nil {
					result.Items = append(result.Items, *work)
				}
			}
			sw.Works = result
			wg.Done()
		}()
	}

	if errplace == nil {
		wg.Add(1)
		go func() {
			placeResults, _ := placeIndex.Search(request)
			result := Result[xmlmodels.Place]{Count: placeResults.Total}
			for _, hit := range placeResults.Hits {
				place := kgpz.Places.Item(hit.ID)
				if place != nil {
					result.Items = append(result.Items, *place)
				}
			}
			sw.Places = result
			wg.Done()
		}()
	}

	if errcategory == nil {
		wg.Add(1)
		go func() {
			categoryResults, _ := categoryIndex.Search(request)
			result := Result[xmlmodels.Category]{Count: categoryResults.Total}
			for _, hit := range categoryResults.Hits {
				category := kgpz.Categories.Item(hit.ID)
				if category != nil {
					result.Items = append(result.Items, *category)
				}
			}
			sw.Categories = result
			wg.Done()
		}()
	}

	if errpiece == nil {
		wg.Add(1)
		go func() {
			pieceResults, _ := pieceIndex.Search(request)
			result := Result[xmlmodels.Piece]{Count: pieceResults.Total}
			for _, hit := range pieceResults.Hits {
				piece := kgpz.Pieces.Item(hit.ID)
				if piece != nil {
					result.Items = append(result.Items, *piece)
				}
			}
			sw.Pieces = result
			wg.Done()
		}()
	}

	if errissue == nil {
		wg.Add(1)
		go func() {
			issueResults, _ := issueIndex.Search(request)
			result := Result[xmlmodels.Issue]{Count: issueResults.Total}
			for _, hit := range issueResults.Hits {
				issue := kgpz.Issues.Item(hit.ID)
				if issue != nil {
					result.Items = append(result.Items, *issue)
				}
			}
			sw.Issues = result
			wg.Done()
		}()
	}

	wg.Wait()

	return &sw, nil
}
