package embed

import (
	"context"
	"database/sql"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strings"
	"time"
)

var urlRegex = regexp.MustCompile(`https?://[^\s<>"{}|\^\[\]]+`)

// Embed represents a stored link preview.
type Embed struct {
	ID          string `json:"id"`
	MessageID   string `json:"message_id"`
	URL         string `json:"url"`
	Title       string `json:"title"`
	Description string `json:"description"`
	ImageURL    string `json:"image_url"`
	SiteName    string `json:"site_name"`
}

// ExtractURLs returns all HTTP/HTTPS URLs found in text.
func ExtractURLs(text string) []string {
	matches := urlRegex.FindAllString(text, -1)
	seen := make(map[string]struct{})
	var out []string
	for _, u := range matches {
		if _, ok := seen[u]; ok {
			continue
		}
		seen[u] = struct{}{}
		out = append(out, u)
	}
	if len(out) > 5 {
		out = out[:5]
	}
	return out
}

// FetchEmbed retrieves OpenGraph meta tags for a URL.
func FetchEmbed(url string) *Embed {
	client := &http.Client{Timeout: 2 * time.Second}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; DumpsterBot/1.0)")
	resp, err := client.Do(req)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil
	}
	body, err := io.ReadAll(io.LimitReader(resp.Body, 256*1024))
	if err != nil {
		return nil
	}
	html := string(body)
	return &Embed{
		URL:         url,
		Title:       ogTag(html, "og:title"),
		Description: ogTag(html, "og:description"),
		ImageURL:    ogTag(html, "og:image"),
		SiteName:    ogTag(html, "og:site_name"),
	}
}

func ogTag(html, property string) string {
	// Try property
	re := regexp.MustCompile(`<meta[^>]+property=["']` + regexp.QuoteMeta(property) + `["'][^>]+content=["']([^"']*)["']`)
	m := re.FindStringSubmatch(html)
	if len(m) > 1 && m[1] != "" {
		return strings.TrimSpace(m[1])
	}
	// Try name
	re = regexp.MustCompile(`<meta[^>]+name=["']` + regexp.QuoteMeta(property) + `["'][^>]+content=["']([^"']*)["']`)
	m = re.FindStringSubmatch(html)
	if len(m) > 1 && m[1] != "" {
		return strings.TrimSpace(m[1])
	}
	// Fallback for title
	if property == "og:title" {
		re = regexp.MustCompile(`<title[^>]*>([^<]+)</title>`)
		m = re.FindStringSubmatch(html)
		if len(m) > 1 {
			return strings.TrimSpace(m[1])
		}
	}
	return ""
}

// FetchAndStore extracts URLs from content, fetches embeds, and stores them.
func FetchAndStore(db *sql.DB, messageID string, content string) {
	urls := ExtractURLs(content)
	if len(urls) == 0 {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()
	for _, url := range urls {
		emb := FetchEmbed(url)
		if emb == nil || (emb.Title == "" && emb.Description == "" && emb.ImageURL == "") {
			continue
		}
		_, err := db.ExecContext(ctx, `
			INSERT INTO embeds (message_id, url, title, description, image_url, site_name)
			VALUES ($1, $2, $3, $4, $5, $6)
		`, messageID, emb.URL, emb.Title, emb.Description, emb.ImageURL, emb.SiteName)
		if err != nil {
			fmt.Printf("failed to store embed: %v\n", err)
		}
	}
}

// LoadForMessages returns embeds grouped by message_id.
func LoadForMessages(ctx context.Context, db *sql.DB, messageIDs []string) (map[string][]Embed, error) {
	result := make(map[string][]Embed)
	if len(messageIDs) == 0 {
		return result, nil
	}
	placeholders := make([]string, len(messageIDs))
	args := make([]interface{}, len(messageIDs))
	for i, id := range messageIDs {
		placeholders[i] = fmt.Sprintf("$%d", i+1)
		args[i] = id
	}
	query := "SELECT message_id, id, url, title, description, image_url, site_name FROM embeds WHERE message_id IN (" + strings.Join(placeholders, ",") + ")"
	rows, err := db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var e Embed
		var mid string
		if err := rows.Scan(&mid, &e.ID, &e.URL, &e.Title, &e.Description, &e.ImageURL, &e.SiteName); err != nil {
			continue
		}
		result[mid] = append(result[mid], e)
	}
	return result, nil
}
