// internal/nostr/events/events.go
package events

import (
	"encoding/json"
	"fmt"
	"time"
	"regexp"
	"strings"

	"github.com/nbd-wtf/go-nostr"
	"git.sovbit.dev/Enki/nostr-poster/internal/models"
)

// EventManager handles creation and signing of Nostr events
type EventManager struct {
	// The private key getter function returns a private key for the given pubkey
	getPrivateKey func(pubkey string) (string, error)
}

// NewEventManager creates a new EventManager
func NewEventManager(getPrivateKey func(pubkey string) (string, error)) *EventManager {
	return &EventManager{
		getPrivateKey: getPrivateKey,
	}
}

// CreateAndSignMetadataEvent creates and signs a kind 0 metadata event for the bot
func (em *EventManager) CreateAndSignMetadataEvent(bot *models.Bot) (*nostr.Event, error) {
	// Create the metadata structure
	metadata := map[string]interface{}{
		"name":     bot.Name,
		"display_name": bot.DisplayName,
		"about":    bot.Bio,
	}

	// Add optional fields if they exist
	if bot.Nip05 != "" {
		metadata["nip05"] = bot.Nip05
	}
	
	if bot.ProfilePicture != "" {
		metadata["picture"] = bot.ProfilePicture
	}
	
	if bot.Banner != "" {
		metadata["banner"] = bot.Banner
	}

	// Convert metadata to JSON
	content, err := json.Marshal(metadata)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal metadata: %w", err)
	}

	// Create the event
	ev := nostr.Event{
		Kind:      0,
		CreatedAt: nostr.Timestamp(time.Now().Unix()),
		Tags:      []nostr.Tag{},
		Content:   string(content),
	}

	// Sign the event
	if err := em.SignEvent(&ev, bot.Pubkey); err != nil {
		return nil, err
	}

	return &ev, nil
}

// CreateAndSignTextNoteEvent creates and signs a kind 1 text note event
func (em *EventManager) CreateAndSignTextNoteEvent(
	pubkey string,
	content string,
	tags []nostr.Tag,
) (*nostr.Event, error) {
	// Create the event
	ev := nostr.Event{
		Kind:      1,
		CreatedAt: nostr.Timestamp(time.Now().Unix()),
		Tags:      tags,
		Content:   content,
	}

	// Sign the event
	if err := em.SignEvent(&ev, pubkey); err != nil {
		return nil, err
	}

	return &ev, nil
}

// CreateAndSignMediaEvent creates and signs a kind 1 event with media attachment
func (em *EventManager) CreateAndSignMediaEvent(
	pubkey string,
	content string,
	mediaURL string,
	mediaType string,
	mediaHash string,
	altText string,
	hashtags []string,
) (*nostr.Event, error) {
	// Create the imeta tag for the media
	imeta := []string{"imeta", "url " + mediaURL, "m " + mediaType}
	
	// Add hash if available
	if mediaHash != "" {
		imeta = append(imeta, "x "+mediaHash)
	}
	
	// Add alt text if available
	if altText != "" {
		imeta = append(imeta, "alt "+altText)
	}
	
	// Create tags
	tags := []nostr.Tag{imeta}
	
	// Add hashtags
	for _, tag := range hashtags {
		tags = append(tags, nostr.Tag{"t", tag})
	}
	
	// Create the event
	ev := nostr.Event{
		Kind:      1,
		CreatedAt: nostr.Timestamp(time.Now().Unix()),
		Tags:      tags,
		Content:   content,
	}
	
	// Sign the event
	if err := em.SignEvent(&ev, pubkey); err != nil {
		return nil, err
	}
	
	return &ev, nil
}

// Updated CreateAndSignPictureEvent to properly format events according to NIP-68
func (em *EventManager) CreateAndSignPictureEvent(
	pubkey string,
	title string,
	description string,
	mediaURL string,
	mediaType string,
	mediaHash string,
	altText string,
	hashtags []string,
) (*nostr.Event, error) {
	// For picture events (kind 20), we need to follow NIP-68 format
	
	// Start with the required title tag
	tags := []nostr.Tag{
		{"title", title},
	}
	
	// Process media URLs from the description
	var contentWithoutURLs string
	
	// If no explicit mediaURL is provided, try to extract from description
	if mediaURL == "" {
		// Simple regex to extract URLs
		re := regexp.MustCompile(`https?://[^\s]+`)
		matches := re.FindAllString(description, -1)
		
		if len(matches) > 0 {
			mediaURL = matches[0]
			
			// Remove the URL from the description for cleaner content
			contentWithoutURLs = re.ReplaceAllString(description, "")
			contentWithoutURLs = strings.TrimSpace(contentWithoutURLs)
		} else {
			// If no URL in description either, we'll just use the description as is
			contentWithoutURLs = description
		}
	} else {
		// We have an explicit mediaURL, make sure it's not in the description
		contentWithoutURLs = strings.ReplaceAll(description, mediaURL, "")
		contentWithoutURLs = strings.TrimSpace(contentWithoutURLs)
	}
	
	// Create the imeta tag according to NIP-92 format
	if mediaURL != "" {
		// imeta tag needs to be an array of strings
		imetaTag := []string{"imeta"}
		
		// Add URL (must be space-delimited key/value)
		imetaTag = append(imetaTag, "url "+mediaURL)
		
		// Add media type (must be space-delimited key/value)
		if mediaType != "" {
			imetaTag = append(imetaTag, "m "+mediaType)
		}
		
		// Add hash if available (must be space-delimited key/value)
		if mediaHash != "" {
			imetaTag = append(imetaTag, "x "+mediaHash)
		}
		
		// Add alt text if available (must be space-delimited key/value)
		if altText != "" {
			imetaTag = append(imetaTag, "alt "+altText)
		}
		
		// Add to tags - nostr.Tag is just a []string so this works
		tags = append(tags, imetaTag)
		
		// Add the media type as a separate 'm' tag for filtering (required by NIP-68)
		if mediaType != "" {
			tags = append(tags, nostr.Tag{"m", mediaType})
		}
		
		// Add the hash as a separate 'x' tag for querying (required by NIP-68)
		if mediaHash != "" {
			tags = append(tags, nostr.Tag{"x", mediaHash})
		}
	}
	
	// Add hashtags
	for _, tag := range hashtags {
		tags = append(tags, nostr.Tag{"t", tag})
	}
	
	// Create the event
	ev := nostr.Event{
		Kind:      20, // NIP-68 Picture Event
		CreatedAt: nostr.Timestamp(time.Now().Unix()),
		Tags:      tags,
		Content:   contentWithoutURLs, // Use the cleaned content without URLs
	}
	
	// Sign the event
	if err := em.SignEvent(&ev, pubkey); err != nil {
		return nil, fmt.Errorf("failed to sign picture event: %w", err)
	}
	
	return &ev, nil
}

// CreateAndSignVideoEvent creates and signs a kind 21/22 video event (NIP-71)
func (em *EventManager) CreateAndSignVideoEvent(
	pubkey string,
	title string,
	description string,
	mediaURL string,
	mediaType string,
	mediaHash string,
	previewImageURL string,
	duration int,
	altText string,
	hashtags []string,
	isShortVideo bool,
) (*nostr.Event, error) {
	// Create the imeta tag for the media
	imeta := []string{
		"imeta",
		"url " + mediaURL,
		"m " + mediaType,
	}
	
	// Add hash if available
	if mediaHash != "" {
		imeta = append(imeta, "x "+mediaHash)
	}
	
	// Add preview image if available
	if previewImageURL != "" {
		imeta = append(imeta, "image "+previewImageURL)
	}
	
	// Create tags
	tags := []nostr.Tag{
		{"title", title},
		imeta,
	}
	
	// Add duration if available
	if duration > 0 {
		tags = append(tags, nostr.Tag{"duration", fmt.Sprintf("%d", duration)})
	}
	
	// Add alt text if available
	if altText != "" {
		tags = append(tags, nostr.Tag{"alt", altText})
	}
	
	// Add hashtags
	for _, tag := range hashtags {
		tags = append(tags, nostr.Tag{"t", tag})
	}
	
	// Choose the right kind based on video type
	kind := 21 // Regular video
	if isShortVideo {
		kind = 22 // Short video
	}
	
	// Create the event
	ev := nostr.Event{
		Kind:      kind,
		CreatedAt: nostr.Timestamp(time.Now().Unix()),
		Tags:      tags,
		Content:   description,
	}
	
	// Sign the event
	if err := em.SignEvent(&ev, pubkey); err != nil {
		return nil, err
	}
	
	return &ev, nil
}

// SignEvent signs a Nostr event using the private key for the given pubkey
func (em *EventManager) SignEvent(ev *nostr.Event, pubkey string) error {
	// Set the public key
	ev.PubKey = pubkey
	
	// Get the private key
	privkey, err := em.getPrivateKey(pubkey)
	if err != nil {
		return fmt.Errorf("failed to get private key: %w", err)
	}
	
	// Sign the event
	return ev.Sign(privkey)
}