package search

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/elastic/go-elasticsearch/v8"
	"github.com/elastic/go-elasticsearch/v8/esapi"
	httperrors "github.com/explorer/backend/libs/go-http-errors"
)

// SearchService handles unified search
type SearchService struct {
	client      *elasticsearch.Client
	indexPrefix string
}

// NewSearchService creates a new search service
func NewSearchService(client *elasticsearch.Client, indexPrefix string) *SearchService {
	return &SearchService{
		client:      client,
		indexPrefix: indexPrefix,
	}
}

// Search performs unified search across all indices
func (s *SearchService) Search(ctx context.Context, query string, chainID *int, limit int) ([]SearchResult, error) {
	// Build search query
	var indices []string
	if chainID != nil {
		indices = []string{
			fmt.Sprintf("%s-blocks-%d", s.indexPrefix, *chainID),
			fmt.Sprintf("%s-transactions-%d", s.indexPrefix, *chainID),
			fmt.Sprintf("%s-addresses-%d", s.indexPrefix, *chainID),
		}
	} else {
		// Search all chains (simplified - would need to enumerate)
		indices = []string{
			fmt.Sprintf("%s-blocks-*", s.indexPrefix),
			fmt.Sprintf("%s-transactions-*", s.indexPrefix),
			fmt.Sprintf("%s-addresses-*", s.indexPrefix),
		}
	}

	searchQuery := map[string]interface{}{
		"query": map[string]interface{}{
			"multi_match": map[string]interface{}{
				"query":  query,
				"fields": []string{"hash", "address", "from_address", "to_address"},
				"type":   "best_fields",
			},
		},
		"size": limit,
	}

	queryJSON, _ := json.Marshal(searchQuery)
	queryString := string(queryJSON)

	// Execute search
	req := esapi.SearchRequest{
		Index:  indices,
		Body:   strings.NewReader(queryString),
		Pretty: true,
	}

	res, err := req.Do(ctx, s.client)
	if err != nil {
		return nil, fmt.Errorf("search failed: %w", err)
	}
	defer res.Body.Close()

	if res.IsError() {
		return nil, fmt.Errorf("elasticsearch error: %s", res.String())
	}

	var result map[string]interface{}
	if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	// Parse results
	results := []SearchResult{}
	if hits, ok := result["hits"].(map[string]interface{}); ok {
		if hitsList, ok := hits["hits"].([]interface{}); ok {
			for _, hit := range hitsList {
				if hitMap, ok := hit.(map[string]interface{}); ok {
					if source, ok := hitMap["_source"].(map[string]interface{}); ok {
						result := s.parseResult(source)
						results = append(results, result)
					}
				}
			}
		}
	}

	return results, nil
}

// SearchResult represents a search result
type SearchResult struct {
	Type    string                 `json:"type"`
	ChainID int                    `json:"chain_id"`
	Data    map[string]interface{} `json:"data"`
	Score   float64                `json:"score"`
}

func (s *SearchService) parseResult(source map[string]interface{}) SearchResult {
	result := SearchResult{
		Data: source,
	}

	if chainID, ok := source["chain_id"].(float64); ok {
		result.ChainID = int(chainID)
	}

	// Determine type based on fields
	if _, ok := source["block_number"]; ok {
		result.Type = "block"
	} else if _, ok := source["transaction_index"]; ok {
		result.Type = "transaction"
	} else if _, ok := source["address"]; ok {
		result.Type = "address"
	}

	return result
}

// HandleSearch handles HTTP search requests
func (s *SearchService) HandleSearch(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		httperrors.WriteJSON(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "Method not allowed")
		return
	}

	query := r.URL.Query().Get("q")
	if query == "" {
		httperrors.WriteJSON(w, http.StatusBadRequest, "BAD_REQUEST", "Query parameter 'q' is required")
		return
	}

	var chainID *int
	if chainIDStr := r.URL.Query().Get("chain_id"); chainIDStr != "" {
		if id, err := strconv.Atoi(chainIDStr); err == nil {
			chainID = &id
		}
	}

	limit := 50
	if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil {
			limit = l
		}
	}

	results, err := s.Search(r.Context(), query, chainID, limit)
	if err != nil {
		httperrors.WriteJSON(w, http.StatusInternalServerError, "INTERNAL_ERROR", "Search failed")
		return
	}

	response := map[string]interface{}{
		"query":  query,
		"results": results,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

