package services

import (
	"bookhoard/internal/database"
	"context"
	"errors"
	"strings"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
)

// SearchService handles all search and filter operations
type SearchService struct {
	db *database.Queries
}

// NewSearchService creates a new search service instance
func NewSearchService(db *database.Queries) *SearchService {
	return &SearchService{db: db}
}

// SearchParams contains parameters for unified search
type SearchParams struct {
	UserID         pgtype.UUID
	LibraryID      pgtype.UUID
	AuthorFilter   string
	SeriesFilter   string
	GenreFilter    string
	TagsFilter     string
	LanguageFilter string
	YearMin        int
	YearMax        int
	HasCover       pgtype.Bool
	SearchQuery    string
	Sort           string
	Limit          int
	Offset         int
}

// parseSearchQuery detects quoted strings for exact match search
// Returns (isExact, processedQuery)
// Examples:
//
//	"asimov" → (false, "asimov")
//	"\"Asimov, Isaac\"" → (true, "Asimov, Isaac")
func (s *SearchService) parseSearchQuery(query string) (bool, string) {
	query = strings.TrimSpace(query)
	if strings.HasPrefix(query, "\"") && strings.HasSuffix(query, "\"") && len(query) >= 2 {
		return true, strings.Trim(query, "\"")
	}
	return false, query
}

// SearchMediaItemsUnified handles combined search + filters
// Supports:
// - Fuzzy text filters (author, series, genre, language)
// - Exact filters (year range, has_cover boolean)
// - Fuzzy search query (or exact match with quotes)
// - Combined search + filters
func (s *SearchService) SearchMediaItemsUnified(ctx context.Context, params SearchParams) ([]database.SearchMediaItemsUnifiedRow, error) {
	// Parse search query for exact match detection
	isExact, searchQuery := s.parseSearchQuery(params.SearchQuery)
	// For now implementing real exact search. If you want to reimplement wildcard patterns uncomment this and line 81 and check queries.sql line 475 and frontend.go line 211
	// searchPattern := ""
	// if isExact {
	// 	searchPattern = "%" + searchQuery + "%"
	// }

	// Build database parameters - only set valid true if filter
	dbParams := database.SearchMediaItemsUnifiedParams{
		UserID:         params.UserID,
		AuthorFilter:   pgtype.Text{String: params.AuthorFilter, Valid: true},
		SeriesFilter:   pgtype.Text{String: params.SeriesFilter, Valid: true},
		GenreFilter:    pgtype.Text{String: params.GenreFilter, Valid: true},
		TagsFilter:     pgtype.Text{String: params.TagsFilter, Valid: true},
		LanguageFilter: pgtype.Text{String: params.LanguageFilter, Valid: true},
		YearMin:        pgtype.Int4{Int32: int32(params.YearMin), Valid: true},
		YearMax:        pgtype.Int4{Int32: int32(params.YearMax), Valid: true},
		HasCover:       params.HasCover,
		SearchQuery:    pgtype.Text{String: searchQuery, Valid: true},
		IsExactSearch:  pgtype.Bool{Bool: isExact, Valid: true},
		// SearchPattern:  pgtype.Text{String: searchPattern, Valid: isExact},
		Sort:   pgtype.Text{String: params.Sort, Valid: true},
		Limit:  pgtype.Int4{Int32: int32(params.Limit), Valid: true},
		Offset: pgtype.Int4{Int32: int32(params.Offset), Valid: true},
	}

	if params.LibraryID.Valid {
		dbParams.LibraryID = params.LibraryID
	}

	// Execute unified search query
	results, err := s.db.SearchMediaItemsUnified(ctx, dbParams)
	if err != nil {
		return nil, err
	}

	return results, nil
}

// FieldSearchParams contains parameters for field-specific search (autocomplete)
type FieldSearchParams struct {
	UserID      pgtype.UUID
	LibraryID   pgtype.UUID
	FieldType   string // "author", "genre", "series", "language"
	SearchQuery string
	Limit       int
	Offset      int
}

// FieldValue represents a single field value with metadata
type FieldValue struct {
	Value string  `json:"value"`
	Count int64   `json:"count"`
	Score float64 `json:"score"`
}

// SearchFieldValues handles field-specific search for autocomplete dropdowns
// Returns distinct values with counts and similarity scores
// Uses 4 separate queries (one per field type) for sqlc v1.30.0 compatibility
func (s *SearchService) SearchFieldValues(ctx context.Context, params FieldSearchParams) ([]FieldValue, error) {
	switch params.FieldType {
	case "author":
		results, err := s.db.SearchAuthorValues(ctx, database.SearchAuthorValuesParams{
			SearchQuery: pgtype.Text{String: params.SearchQuery, Valid: true},
			UserID:      params.UserID,
			LibraryID:   params.LibraryID,
			Limit:       pgtype.Int4{Int32: int32(params.Limit), Valid: true},
			Offset:      pgtype.Int4{Int32: int32(params.Offset), Valid: true},
		})
		if err != nil {
			return nil, err
		}
		fieldValues := make([]FieldValue, len(results))
		for i, r := range results {
			fieldValues[i] = FieldValue{Value: r.Value.String, Count: r.Count, Score: r.Score}
		}
		return fieldValues, nil

	case "genre":
		results, err := s.db.SearchGenreValues(ctx, database.SearchGenreValuesParams{
			SearchQuery: pgtype.Text{String: params.SearchQuery, Valid: true},
			UserID:      params.UserID,
			LibraryID:   params.LibraryID,
			Limit:       pgtype.Int4{Int32: int32(params.Limit), Valid: true},
			Offset:      pgtype.Int4{Int32: int32(params.Offset), Valid: true},
		})
		if err != nil {
			return nil, err
		}
		fieldValues := make([]FieldValue, len(results))
		for i, r := range results {
			fieldValues[i] = FieldValue{Value: r.Value.String, Count: r.Count, Score: r.Score}
		}
		return fieldValues, nil

	case "tags":
		results, err := s.db.SearchTagsValues(ctx, database.SearchTagsValuesParams{
			SearchQuery: pgtype.Text{String: params.SearchQuery, Valid: true},
			UserID:      params.UserID,
			LibraryID:   params.LibraryID,
			Limit:       pgtype.Int4{Int32: int32(params.Limit), Valid: true},
			Offset:      pgtype.Int4{Int32: int32(params.Offset), Valid: true},
		})
		if err != nil {
			return nil, err
		}
		fieldValues := make([]FieldValue, len(results))
		for i, r := range results {
			fieldValues[i] = FieldValue{Value: r.Value, Count: r.Count, Score: r.Score}
		}
		return fieldValues, nil

	case "series":
		results, err := s.db.SearchSeriesValues(ctx, database.SearchSeriesValuesParams{
			SearchQuery: pgtype.Text{String: params.SearchQuery, Valid: true},
			UserID:      params.UserID,
			LibraryID:   params.LibraryID,
			Limit:       pgtype.Int4{Int32: int32(params.Limit), Valid: true},
			Offset:      pgtype.Int4{Int32: int32(params.Offset), Valid: true},
		})
		if err != nil {
			return nil, err
		}
		fieldValues := make([]FieldValue, len(results))
		for i, r := range results {
			fieldValues[i] = FieldValue{Value: r.Value.String, Count: r.Count, Score: r.Score}
		}
		return fieldValues, nil

	case "language":
		results, err := s.db.SearchLanguageValues(ctx, database.SearchLanguageValuesParams{
			SearchQuery: pgtype.Text{String: params.SearchQuery, Valid: true},
			UserID:      params.UserID,
			LibraryID:   params.LibraryID,
			Limit:       pgtype.Int4{Int32: int32(params.Limit), Valid: true},
			Offset:      pgtype.Int4{Int32: int32(params.Offset), Valid: true},
		})
		if err != nil {
			return nil, err
		}
		fieldValues := make([]FieldValue, len(results))
		for i, r := range results {
			fieldValues[i] = FieldValue{Value: r.Value.String, Count: r.Count, Score: r.Score}
		}
		return fieldValues, nil

	default:
		return []FieldValue{}, nil
	}
}

// ExecuteSearch performs search and returns results with count
// Shared between JSON endpoint and HTML rendering
func (s *SearchService) ExecuteSearch(ctx context.Context, params SearchParams) ([]database.SearchMediaItemsUnifiedRow, int, error) {
	results, err := s.SearchMediaItemsUnified(ctx, params)
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return nil, 0, err
	}

	count := len(results)
	return results, count, nil
}
