// kafka — a privacy-respecting metasearch engine
// Copyright (C) 2026-present metamorphosis-dev
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package autocomplete

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Service fetches search suggestions from upstream or Wikipedia OpenSearch.
type Service struct {
	upstreamURL string
	http       *http.Client
}

func NewService(upstreamURL string, timeout time.Duration) *Service {
	if timeout <= 0 {
		timeout = 5 * time.Second
	}
	return &Service{
		upstreamURL: strings.TrimRight(upstreamURL, "/"),
		http: &http.Client{Timeout: timeout},
	}
}

func (s *Service) Suggestions(ctx context.Context, query string) ([]string, error) {
	if strings.TrimSpace(query) == "" {
		return nil, nil
	}

	if s.upstreamURL != "" {
		return s.upstreamSuggestions(ctx, query)
	}
	return s.wikipediaSuggestions(ctx, query)
}

func (s *Service) upstreamSuggestions(ctx context.Context, query string) ([]string, error) {
	u := s.upstreamURL + "/autocompleter?" + url.Values{"q": {query}}.Encode()
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/json")

	resp, err := s.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New("upstream autocompleter failed")
	}

	body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
	if err != nil {
		return nil, err
	}

	// The /autocompleter endpoint returns a plain JSON array of strings.
	var out []string
	if err := json.Unmarshal(body, &out); err != nil {
		return nil, err
	}
	return out, nil
}

// wikipediaSuggestions fetches suggestions from Wikipedia's OpenSearch API.
func (s *Service) wikipediaSuggestions(ctx context.Context, query string) ([]string, error) {
	u := "https://en.wikipedia.org/w/api.php?" + url.Values{
		"action":       {"opensearch"},
		"format":       {"json"},
		"formatversion": {"2"},
		"search":       {query},
		"namespace":    {"0"},
		"limit":        {"10"},
	}.Encode()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set(
		"User-Agent",
		"gosearch-go/0.1 (compatible; +https://github.com/metamorphosis-dev/kafka)",
	)

	resp, err := s.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New("wikipedia opensearch failed")
	}

	body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
	if err != nil {
		return nil, err
	}

	// Wikipedia OpenSearch returns: [query, [suggestions], ...]
	var data []json.RawMessage
	if err := json.Unmarshal(body, &data); err != nil {
		return nil, err
	}
	if len(data) < 2 {
		return nil, nil
	}

	var suggestions []string
	if err := json.Unmarshal(data[1], &suggestions); err != nil {
		return nil, err
	}
	return suggestions, nil
}
