package meiliService

import (
	"fmt"
	"time"

	"git.ma-al.com/goc_daniel/b2b/app/config"
	"git.ma-al.com/goc_daniel/b2b/app/model"
	"git.ma-al.com/goc_daniel/b2b/app/repos/productDescriptionRepo"
	constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
	"github.com/meilisearch/meilisearch-go"
)

// MeiliIndexSettings holds the configurable index settings
type MeiliIndexSettings struct {
	SearchableAttributes []string `json:"searchableAttributes"`
	DisplayedAttributes  []string `json:"displayedAttributes"`
	FilterableAttributes []string `json:"filterableAttributes"`
	SortableAttributes   []string `json:"sortableAttributes"`
}

type MeiliService struct {
	productDescriptionRepo productDescriptionRepo.UIProductDescriptionRepo
	meiliClient            meilisearch.ServiceManager
}

func New() *MeiliService {

	client := meilisearch.New(
		config.Get().MailiSearch.ServerURL,
		meilisearch.WithAPIKey(config.Get().MailiSearch.ApiKey),
	)

	return &MeiliService{
		meiliClient:            client,
		productDescriptionRepo: productDescriptionRepo.New(),
	}
}

func GetIndexName(id_lang uint) string {
	return fmt.Sprintf("shop_%d_lang_%d", constdata.SHOP_ID, id_lang)
}

// ====================================   FOR TESTING ONLY   ====================================
func (s *MeiliService) CreateIndex(id_lang uint) error {
	indexName := GetIndexName(id_lang)

	const batchSize = 500
	offset := 0

	for {
		// Get batch of products from repo (includes scanning)
		products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang, offset, batchSize)
		if err != nil {
			return fmt.Errorf("failed to get products batch at offset %d: %w", offset, err)
		}

		// If no products returned, we're done
		if len(products) == 0 {
			break
		}

		// Add batch to index
		if err := s.addBatchToIndex(indexName, products); err != nil {
			return fmt.Errorf("failed to add batch to index: %w", err)
		}

		// Update offset for next batch
		offset += batchSize

		fmt.Printf("Indexed %d products (offset: %d)\n", len(products), offset)
	}

	// Configure filterable attributes
	filterableAttributes := []interface{}{
		"product_id",
		"category_id",
		"category_ids",
		"attr",
		"feat",
		"variations",
		"price",
	}
	task, err := s.meiliClient.Index(indexName).UpdateFilterableAttributes(&filterableAttributes)
	if err != nil {
		return fmt.Errorf("failed to update filterable attributes: %w", err)
	}
	_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
	if err != nil {
		return fmt.Errorf("failed to wait for filterable task: %w", err)
	}

	// Configure sortable attributes
	sortableAttributes := []string{
		"price",
		"name",
		"product_id",
		"name",
		"category_ids",
	}
	task, err = s.meiliClient.Index(indexName).UpdateSortableAttributes(&sortableAttributes)
	if err != nil {
		return fmt.Errorf("failed to update sortable attributes: %w", err)
	}

	task, err = s.meiliClient.Index(indexName).UpdateSettings(&meilisearch.Settings{
		LocalizedAttributes: indexatio_params[id_lang].LocalizedAttributes,
		Synonyms:            indexatio_params[id_lang].Synonyms,
		StopWords:           indexatio_params[id_lang].StopWords,
	})
	if err != nil {
		return fmt.Errorf("failed to update ranking rules: %w", err)
	}

	_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
	if err != nil {
		return fmt.Errorf("failed to wait for sortable task: %w", err)
	}

	// Configure displayed attributes
	displayedAttributes := []string{
		"product_id",
		"name",
		"ean13",
		"reference",
		"variations",
		"id_image",
		"price",
		"category_name",
	}
	task, err = s.meiliClient.Index(indexName).UpdateDisplayedAttributes(&displayedAttributes)
	if err != nil {
		return fmt.Errorf("failed to update displayed attributes: %w", err)
	}
	_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
	if err != nil {
		return fmt.Errorf("failed to wait for displayed task: %w", err)
	}

	// Configure searchable attributes
	searchableAttributes := []string{
		"name",
		"description",
		"ean13",
		"category_name",
		"reference",
	}
	task, err = s.meiliClient.Index(indexName).UpdateSearchableAttributes(&searchableAttributes)
	if err != nil {
		return fmt.Errorf("failed to update searchable attributes: %w", err)
	}

	task, err = s.meiliClient.Index(indexName).UpdateFaceting(&meilisearch.Faceting{MaxValuesPerFacet: 5})
	if err != nil {
		return fmt.Errorf("failed to update ranking rules: %w", err)
	}

	// keyRequest := &meilisearch.Key{
	// 	Key:         "my secret key", // ✅ provide your own token
	// 	Description: "Search-only key for frontend",
	// 	Actions:     []string{"search", "settings.get"},
	// 	Indexes:     []string{indexName}, // restrict to your index
	// }

	// createdKey, err := s.meiliClient.CreateKey(keyRequest)
	// if err != nil {
	// 	panic(err)
	// }
	// fmt.Println("Custom search key created:", createdKey.Key)

	_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
	if err != nil {
		return fmt.Errorf("failed to wait for searchable task: %w", err)
	}

	return nil
}

// addBatchToIndex adds a batch of products to the Meilisearch index
func (s *MeiliService) addBatchToIndex(indexName string, products []model.MeiliSearchProduct) error {
	primaryKey := "product_id"
	docOptions := &meilisearch.DocumentOptions{
		PrimaryKey:   &primaryKey,
		SkipCreation: false,
	}

	task, err := s.meiliClient.Index(indexName).AddDocuments(products, docOptions)
	if err != nil {
		return fmt.Errorf("failed to add documents: %w", err)
	}

	finishedTask, err := s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
	if err != nil {
		return fmt.Errorf("failed to wait for task: %w", err)
	}
	if finishedTask.Status == "failed" {
		return fmt.Errorf("task failed: %v", finishedTask.Error)
	}

	return nil
}

// Search performs a full-text search on the specified index
func (s *MeiliService) Search(id_lang uint, query string, id_category uint) (meilisearch.SearchResponse, error) {
	indexName := GetIndexName(id_lang)

	filter_query := "Active = 1"
	if id_category != 0 {
		// Use CategoryIDs to include products from child categories
		filter_query += fmt.Sprintf(" AND CategoryIDs = %d", id_category)
	}

	searchReq := &meilisearch.SearchRequest{
		Limit: 30,
		Facets: []string{
			"CategoryID",
		},
		Filter: filter_query,
	}

	results, err := s.meiliClient.Index(indexName).Search(query, searchReq)
	if err != nil {
		fmt.Printf("Meilisearch search error: %v\n", err)
		return meilisearch.SearchResponse{}, err
	}

	return *results, nil
}

// HealthCheck checks if Meilisearch is healthy and accessible
func (s *MeiliService) HealthCheck() (*meilisearch.Health, error) {
	health, err := s.meiliClient.Health()
	if err != nil {
		return nil, fmt.Errorf("meilisearch health check failed: %w", err)
	}

	return health, nil
}

// GetIndexSettings retrieves the current settings for a specific index
func (s *MeiliService) GetIndexSettings(id_lang uint) (map[string]interface{}, error) {
	indexName := GetIndexName(id_lang)

	index := s.meiliClient.Index(indexName)

	result := make(map[string]interface{})

	// Get searchable attributes
	searchable, err := index.GetSearchableAttributes()
	if err == nil {
		result["searchableAttributes"] = searchable
	}

	// Get filterable attributes
	filterable, err := index.GetFilterableAttributes()
	if err == nil {
		result["filterableAttributes"] = filterable
	}

	// Get displayed attributes
	displayed, err := index.GetDisplayedAttributes()
	if err == nil {
		result["displayedAttributes"] = displayed
	}

	// Get ranking rules
	ranking, err := index.GetRankingRules()
	if err == nil {
		result["rankingRules"] = ranking
	}

	// Get distinct attribute
	distinct, err := index.GetDistinctAttribute()
	if err == nil && distinct != nil {
		result["distinctAttribute"] = *distinct
	}

	// Get typo tolerance
	typo, err := index.GetTypoTolerance()
	if err == nil {
		result["typoTolerance"] = typo
	}

	return result, nil
}

// UpdateIndexSettings updates the index settings (searchable, displayed, filterable, sortable attributes)
func (s *MeiliService) UpdateIndexSettings(id_lang uint, settings MeiliIndexSettings) error {
	indexName := GetIndexName(id_lang)
	index := s.meiliClient.Index(indexName)

	// Update searchable attributes
	if len(settings.SearchableAttributes) > 0 {
		task, err := index.UpdateSearchableAttributes(&settings.SearchableAttributes)
		if err != nil {
			return fmt.Errorf("failed to update searchable attributes: %w", err)
		}
		_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
		if err != nil {
			return fmt.Errorf("failed to wait for searchable attributes task: %w", err)
		}
	}

	// Update displayed attributes
	if len(settings.DisplayedAttributes) > 0 {
		task, err := index.UpdateDisplayedAttributes(&settings.DisplayedAttributes)
		if err != nil {
			return fmt.Errorf("failed to update displayed attributes: %w", err)
		}
		_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
		if err != nil {
			return fmt.Errorf("failed to wait for displayed attributes task: %w", err)
		}
	}

	// Update filterable attributes
	if len(settings.FilterableAttributes) > 0 {
		var filterable []interface{}
		for _, attr := range settings.FilterableAttributes {
			filterable = append(filterable, attr)
		}
		task, err := index.UpdateFilterableAttributes(&filterable)
		if err != nil {
			return fmt.Errorf("failed to update filterable attributes: %w", err)
		}
		_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
		if err != nil {
			return fmt.Errorf("failed to wait for filterable attributes task: %w", err)
		}
	}

	// Update sortable attributes
	if len(settings.SortableAttributes) > 0 {
		task, err := index.UpdateSortableAttributes(&settings.SortableAttributes)
		if err != nil {
			return fmt.Errorf("failed to update sortable attributes: %w", err)
		}
		_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
		if err != nil {
			return fmt.Errorf("failed to wait for sortable attributes task: %w", err)
		}
	}

	return nil
}
