package search

import (
	"context"
	"net/http"
	"time"

	"github.com/ashie/gosearch/internal/engines"
	"github.com/ashie/gosearch/internal/contracts"
	"github.com/ashie/gosearch/internal/upstream"
)

type ServiceConfig struct {
	UpstreamURL string
	HTTPTimeout time.Duration
}

type Service struct {
	upstreamClient *upstream.Client
	planner        *engines.Planner
	localEngines   map[string]engines.Engine
}

func NewService(cfg ServiceConfig) *Service {
	timeout := cfg.HTTPTimeout
	if timeout <= 0 {
		timeout = 10 * time.Second
	}

	httpClient := &http.Client{Timeout: timeout}

	var up *upstream.Client
	if cfg.UpstreamURL != "" {
		c, err := upstream.NewClient(cfg.UpstreamURL, timeout)
		if err == nil {
			up = c
		}
	}

	return &Service{
		upstreamClient: up,
		planner:        engines.NewPlannerFromEnv(),
		localEngines:  engines.NewDefaultPortedEngines(httpClient),
	}
}

func (s *Service) Search(ctx context.Context, req SearchRequest) (SearchResponse, error) {
	localEngines, upstreamEngines, _ := s.planner.Plan(req)

	responses := make([]contracts.SearchResponse, 0, 2)
	upstreamSet := map[string]bool{}
	for _, e := range upstreamEngines {
		upstreamSet[e] = true
	}

	for _, engineName := range localEngines {
		eng, ok := s.localEngines[engineName]
		if !ok {
			continue
		}
		r, err := eng.Search(ctx, req)
		if err != nil {
			// MVP: fail fast so the client sees a real error.
			return SearchResponse{}, err
		}
		responses = append(responses, r)

		// Some engines (notably qwant due to anti-bot protections) can return
		// zero local results depending on client/IP. If upstream SearXNG is
		// configured, let it attempt the same engine as a fallback.
		if shouldFallbackToUpstream(engineName, r) && !upstreamSet[engineName] {
			upstreamEngines = append(upstreamEngines, engineName)
			upstreamSet[engineName] = true
		}
	}

	if s.upstreamClient != nil && len(upstreamEngines) > 0 {
		r, err := s.upstreamClient.SearchJSON(ctx, req, upstreamEngines)
		if err != nil {
			return SearchResponse{}, err
		}
		responses = append(responses, r)
	}

	if len(responses) == 0 {
		return SearchResponse{
			Query:               req.Query,
			NumberOfResults:    0,
			Results:            []MainResult{},
			Answers:            []map[string]any{},
			Corrections:       []string{},
			Infoboxes:          []map[string]any{},
			Suggestions:        []string{},
			UnresponsiveEngines: [][2]string{},
		}, nil
	}

	merged := MergeResponses(responses)
	if merged.Query == "" {
		merged.Query = req.Query
	}
	return merged, nil
}

func shouldFallbackToUpstream(engineName string, r contracts.SearchResponse) bool {
	if engineName != "qwant" {
		return false
	}
	return len(r.Results) == 0 && len(r.Answers) == 0 && len(r.Infoboxes) == 0
}

