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

package engines

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

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

// DuckDuckGoImagesEngine searches DuckDuckGo Images via their vql API.
type DuckDuckGoImagesEngine struct {
	client *http.Client
}

func (e *DuckDuckGoImagesEngine) Name() string { return "ddg_images" }

func (e *DuckDuckGoImagesEngine) Search(ctx context.Context, req contracts.SearchRequest) (contracts.SearchResponse, error) {
	if e == nil || e.client == nil {
		return contracts.SearchResponse{}, errors.New("ddg_images engine not initialized")
	}
	q := strings.TrimSpace(req.Query)
	if q == "" {
		return contracts.SearchResponse{Query: req.Query}, nil
	}

	// Step 1: Get a VQD token from the initial search page.
	vqd, err := e.getVQD(ctx, q)
	if err != nil {
		return contracts.SearchResponse{
			Query:               req.Query,
			UnresponsiveEngines: [][2]string{{"ddg_images", "vqd_fetch_failed"}},
			Results:             []contracts.MainResult{},
			Answers:             []map[string]any{},
			Corrections:         []string{},
			Infoboxes:           []map[string]any{},
			Suggestions:         []string{},
		}, nil
	}

	// Step 2: Fetch image results using the VQD token.
	endpoint := fmt.Sprintf(
		"https://duckduckgo.com/i.js?q=%s&kl=wt-wt&l=wt-wt&p=1&s=%d&vqd=%s",
		url.QueryEscape(q),
		(req.Pageno-1)*50,
		url.QueryEscape(vqd),
	)

	httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return contracts.SearchResponse{}, err
	}
	httpReq.Header.Set("User-Agent", "kafka/0.1 (compatible; +https://git.ashisgreat.xyz/penal-colony/kafka)")
	httpReq.Header.Set("Referer", "https://duckduckgo.com/")

	resp, err := e.client.Do(httpReq)
	if err != nil {
		return contracts.SearchResponse{}, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		io.Copy(io.Discard, io.LimitReader(resp.Body, 16*1024))
		return contracts.SearchResponse{}, fmt.Errorf("ddg_images upstream error: status %d", resp.StatusCode)
	}

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

	return parseDDGImages(body, req.Query)
}

// getVQD fetches a VQD token from DuckDuckGo's search page.
func (e *DuckDuckGoImagesEngine) getVQD(ctx context.Context, query string) (string, error) {
	endpoint := "https://duckduckgo.com/?q=" + url.QueryEscape(query)

	httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return "", err
	}
	httpReq.Header.Set("User-Agent", "kafka/0.1 (compatible; +https://git.ashisgreat.xyz/penal-colony/kafka)")

	resp, err := e.client.Do(httpReq)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

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

	// Extract VQD from the HTML: vqd='...'
	vqd := extractVQD(string(body))
	if vqd == "" {
		return "", fmt.Errorf("vqd token not found in response")
	}
	return vqd, nil
}

// extractVQD extracts the VQD token from DuckDuckGo's HTML response.
func extractVQD(html string) string {
	// Look for: vqd='...' or vqd="..."
	for _, prefix := range []string{"vqd='", `vqd="`} {
		idx := strings.Index(html, prefix)
		if idx == -1 {
			continue
		}
		start := idx + len(prefix)
		end := start
		for end < len(html) && html[end] != '\'' && html[end] != '"' {
			end++
		}
		if end > start {
			return html[start:end]
		}
	}
	return ""
}

// ddgImageResult represents a single image result from DDG's JSON API.
type ddgImageResult struct {
	Title   string `json:"title"`
	URL     string `json:"url"`
	Thumbnail string `json:"thumbnail"`
	Image   string `json:"image"`
	Width   int    `json:"width"`
	Height  int    `json:"height"`
	Source  string `json:"source"`
}

func parseDDGImages(body []byte, query string) (contracts.SearchResponse, error) {
	var results struct {
		Results []ddgImageResult `json:"results"`
	}

	if err := json.Unmarshal(body, &results); err != nil {
		return contracts.SearchResponse{}, fmt.Errorf("ddg_images JSON parse error: %w", err)
	}

	out := make([]contracts.MainResult, 0, len(results.Results))
	for _, img := range results.Results {
		if img.URL == "" {
			continue
		}

		// Prefer the full image URL as thumbnail, fall back to the thumbnail field.
		thumb := img.Image
		if thumb == "" {
			thumb = img.Thumbnail
		}

		// Build a simple content string showing dimensions.
		content := ""
		if img.Width > 0 && img.Height > 0 {
			content = strconv.Itoa(img.Width) + " × " + strconv.Itoa(img.Height)
		}
		if img.Source != "" {
			if content != "" {
				content += " — " + img.Source
			} else {
				content = img.Source
			}
		}

		urlPtr := img.URL
		out = append(out, contracts.MainResult{
			Template:  "images",
			Title:     img.Title,
			Content:   content,
			URL:       &urlPtr,
			Thumbnail: thumb,
			Engine:    "ddg_images",
			Score:     0,
			Category:  "images",
			Engines:   []string{"ddg_images"},
		})
	}

	return contracts.SearchResponse{
		Query:               query,
		NumberOfResults:    len(out),
		Results:             out,
		Answers:             []map[string]any{},
		Corrections:         []string{},
		Infoboxes:           []map[string]any{},
		Suggestions:         []string{},
		UnresponsiveEngines: [][2]string{},
	}, nil
}
