package backend

import (
	"fmt"
	"sync"

	"github.com/blevesearch/bleve"
)

type (
	Index struct {
		index bleve.Index
		mu    sync.RWMutex
	}

	Result struct{ *bleve.SearchResult }
)

func (i *Index) isIndexed(filename string, errChan chan<- error) bool {
	i.mu.RLock()
	defer i.mu.RUnlock()

	query := bleve.NewDocIDQuery([]string{filename})
	request := bleve.NewSearchRequest(query)
	result, err := i.index.Search(request)
	if err != nil {
		errChan <- fmt.Errorf("error searching for file %v in index: %v", filename, err)
		return false
	}

	return len(result.Hits) > 0
}

func (i *Index) indexAllArticles(c *Config, db *DB) error {
	articles, err := db.GetAllArticles()
	if err != nil {
		return fmt.Errorf("error getting all articles from DB: %v", err)
	}

	var wg sync.WaitGroup
	errChan := make(chan error, len(articles))

	go func() {
		wg.Wait()
		close(errChan)
	}()

	for _, article := range articles {
		wg.Add(1)
		go func() {
			defer wg.Done()

			filename := article.UUID.String()
			content, err := article.ReadContent(c)
			if err != nil {
				errChan <- fmt.Errorf("error reading content of article %v: %v", filename, err)
				return
			}

			if !i.isIndexed(filename, errChan) {
				if err := i.Add(filename, content); err != nil {
					errChan <- fmt.Errorf("error adding file %v to index: %v", filename, err)
					return
				}
			}

			errChan <- nil
		}()
	}
	if err = <-errChan; err != nil {
		return fmt.Errorf("error(s) indexing all articles: %v", err)
	}

	return nil
}

func InitIndex(c *Config) (*Index, error) {
	var err error
	index := new(Index)

	index.index, err = bleve.Open(c.IndexDir)
	if err != nil {
		mapping := bleve.NewIndexMapping()
		index.index, err = bleve.New(c.IndexDir, mapping)
		if err != nil {
			return nil, fmt.Errorf("error creating new index file: %v", err)
		}
	}

	return index, err
}

func (i *Index) Add(filename, content string) error {
	i.mu.Lock()
	defer i.mu.Unlock()

	doc := map[string]string{
		"name": filename,
		"body": content,
	}

	if err := i.index.Index(filename, doc); err != nil {
		return fmt.Errorf("error indexing file %v: %v", filename, err)
	}

	return nil
}

func (i *Index) Query(q string) (*Result, error) {
	i.mu.RLock()
	defer i.mu.RUnlock()

	query := bleve.NewQueryStringQuery(q)
	request := bleve.NewSearchRequest(query)
	result, err := i.index.Search(request)
	if err != nil {
		return nil, fmt.Errorf("error querying index for \"%v\": %v", q, err)
	}

	if len(result.Hits) < 1 {
		return nil, nil
	}

	return &Result{result}, err
}

func (i *Index) Delete(filename string) error {
	i.mu.Lock()
	defer i.mu.Unlock()

	if err := i.index.Delete(filename); err != nil {
		return fmt.Errorf("error deleting file %v from index: %v", filename, err)
	}

	return nil
}