package pipeline

import (
	"database/sql"
	"encoding/json"
	"fmt"

	"pinscrape-allgo/internal/clients/pinterest"
	"pinscrape-allgo/internal/util"
)

func marshalImages(images []pinterest.Result) string {
	type img struct {
		Title    string `json:"title"`
		ImageURL string `json:"image_url"`
	}
	out := make([]img, 0, len(images))
	for _, r := range images {
		out = append(out, img{Title: r.Title, ImageURL: r.ImageURL})
	}
	b, err := json.Marshal(out)
	if err != nil {
		return "[]"
	}
	return string(b)
}

func marshalSnippet(related, descriptions []string) string {
	if related == nil {
		related = []string{}
	}
	if descriptions == nil {
		descriptions = []string{}
	}
	payload := map[string]interface{}{
		"related_kw":  related,
		"description": descriptions,
	}
	b, err := json.Marshal(payload)
	if err != nil {
		return `{"related_kw":[],"description":[]}`
	}
	return string(b)
}

type snippetData struct {
	RelatedKW   []string `json:"related_kw"`
	Description []string `json:"description"`
}

func parseSnippet(raw string) snippetData {
	var data snippetData
	if raw == "" {
		return data
	}
	if err := json.Unmarshal([]byte(raw), &data); err != nil {
		return snippetData{}
	}
	return data
}

// mergeSnippet fills only the empty half of an existing snippet: newly
// scraped values win when present, otherwise the old values are kept. Nothing
// already scraped is ever lost.
func mergeSnippet(existing string, related, descriptions []string) string {
	old := parseSnippet(existing)
	if len(related) == 0 {
		related = old.RelatedKW
	}
	if len(descriptions) == 0 {
		descriptions = old.Description
	}
	return marshalSnippet(related, descriptions)
}

// appendKeywords inserts new keywords with dedupe (parity with
// pinscrape/database.py append_keywords).
func appendKeywords(conn *sql.DB, keywords []string) (int, error) {
	existing := map[string]bool{}
	rows, err := conn.Query("SELECT keyword FROM posts")
	if err != nil {
		return 0, err
	}
	for rows.Next() {
		var kw string
		if err := rows.Scan(&kw); err != nil {
			rows.Close()
			return 0, err
		}
		existing[kw] = true
	}
	rows.Close()
	if err := rows.Err(); err != nil {
		return 0, err
	}

	tx, err := conn.Begin()
	if err != nil {
		return 0, err
	}
	stmt, err := tx.Prepare("INSERT INTO posts (keyword, slug, status) VALUES (?, ?, 0)")
	if err != nil {
		tx.Rollback()
		return 0, err
	}
	defer stmt.Close()

	added := 0
	for _, kw := range keywords {
		kw = trimSpace(kw)
		if kw == "" || existing[kw] {
			continue
		}
		if _, err := stmt.Exec(kw, util.Slugify(kw)); err != nil {
			tx.Rollback()
			return 0, fmt.Errorf("insert %q: %w", kw, err)
		}
		existing[kw] = true
		added++
	}
	if err := tx.Commit(); err != nil {
		return 0, err
	}
	return added, nil
}

func trimSpace(s string) string {
	start := 0
	for start < len(s) && (s[start] == ' ' || s[start] == '\t' || s[start] == '\n' || s[start] == '\r') {
		start++
	}
	end := len(s)
	for end > start && (s[end-1] == ' ' || s[end-1] == '\t' || s[end-1] == '\n' || s[end-1] == '\r') {
		end--
	}
	return s[start:end]
}
