// 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 httpapi_test

import (
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/metamorphosis-dev/kafka/internal/contracts"
	"github.com/metamorphosis-dev/kafka/internal/httpapi"
	"github.com/metamorphosis-dev/kafka/internal/search"
)

// mockUpstreamHandler returns controlled JSON responses.
func mockUpstreamJSON(query string) contracts.SearchResponse {
	return contracts.SearchResponse{
		Query:            query,
		NumberOfResults:  2,
		Results: []contracts.MainResult{
			{Title: "Upstream Result 1", URL: ptr("https://upstream.example/1"), Content: "From upstream", Engine: "upstream"},
			{Title: "Upstream Result 2", URL: ptr("https://upstream.example/2"), Content: "From upstream", Engine: "upstream"},
		},
		Answers:              []map[string]any{},
		Corrections:          []string{},
		Infoboxes:            []map[string]any{},
		Suggestions:          []string{"upstream suggestion"},
		UnresponsiveEngines:  [][2]string{},
	}
}

func ptr(s string) *string { return &s }

func newTestServer(t *testing.T) (*httptest.Server, *httpapi.Handler) {
	t.Helper()

	// Mock upstream server that returns controlled JSON.
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		query := r.FormValue("q")
		resp := mockUpstreamJSON(query)
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(resp)
	}))
	t.Cleanup(upstream.Close)

	svc := search.NewService(search.ServiceConfig{
		UpstreamURL:   upstream.URL,
		HTTPTimeout:   0,
		Cache:         nil,
		EnginesConfig: nil,
	})

	h := httpapi.NewHandler(svc, nil, "https://src.example.com")

	mux := http.NewServeMux()
	mux.HandleFunc("/healthz", h.Healthz)
	mux.HandleFunc("/", h.Index)
	mux.HandleFunc("/search", h.Search)
	mux.HandleFunc("/autocompleter", h.Autocompleter)

	server := httptest.NewServer(mux)
	t.Cleanup(server.Close)
	return server, h
}

func TestHealthz(t *testing.T) {
	server, _ := newTestServer(t)
	resp, err := http.Get(server.URL + "/healthz")
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
	if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "text/plain") {
		t.Errorf("expected text/plain, got %s", ct)
	}
}

func TestIndex(t *testing.T) {
	server, _ := newTestServer(t)
	resp, err := http.Get(server.URL + "/")
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
	if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "text/html") {
		t.Errorf("expected text/html, got %s", ct)
	}

	body, _ := io.ReadAll(resp.Body)
	if !strings.Contains(string(body), "<!DOCTYPE html") {
		t.Error("expected HTML DOCTYPE")
	}
}

func TestSearch_RedirectOnEmptyQuery(t *testing.T) {
	server, _ := newTestServer(t)
	client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error {
		return http.ErrUseLastResponse
	}}
	req, _ := http.NewRequest("GET", server.URL+"/search?format=html", nil)
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusFound {
		t.Errorf("expected redirect 302, got %d", resp.StatusCode)
	}
	loc, _ := resp.Location()
	if loc == nil || loc.Path != "/" {
		t.Errorf("expected redirect to /, got %v", loc)
	}
}

func TestSearch_JSONResponse(t *testing.T) {
	server, _ := newTestServer(t)
	resp, err := http.Get(server.URL + "/search?q=test&format=json")
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
	if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") {
		t.Errorf("expected application/json, got %s", ct)
	}

	var result contracts.SearchResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		t.Fatalf("failed to decode JSON: %v", err)
	}
	if result.Query != "test" {
		t.Errorf("expected query 'test', got %q", result.Query)
	}
	if len(result.Results) == 0 {
		t.Error("expected at least one result")
	}
}

func TestSearch_HTMLResponse(t *testing.T) {
	server, _ := newTestServer(t)
	resp, err := http.Get(server.URL + "/search?q=test&format=html")
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
	if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "text/html") {
		t.Errorf("expected text/html, got %s", ct)
	}

	body, _ := io.ReadAll(resp.Body)
	if !strings.Contains(string(body), "<!DOCTYPE html") {
		t.Error("expected HTML DOCTYPE in response")
	}
}

func TestAutocompleter_EmptyQuery(t *testing.T) {
	server, _ := newTestServer(t)
	resp, err := http.Get(server.URL + "/autocompleter?q=")
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d", resp.StatusCode)
	}
}

func TestAutocompleter_NoQuery(t *testing.T) {
	server, _ := newTestServer(t)
	resp, err := http.Get(server.URL + "/autocompleter")
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d", resp.StatusCode)
	}
}

func TestSearch_SourceURLInFooter(t *testing.T) {
	server, _ := newTestServer(t)
	resp, err := http.Get(server.URL + "/?q=test")
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	if !strings.Contains(string(body), "https://src.example.com") {
		t.Error("expected source URL in footer")
	}
	if !strings.Contains(string(body), "AGPLv3") {
		t.Error("expected AGPLv3 link in footer")
	}
}
