package nostr

import (
	"context"
	"encoding/hex"
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/nbd-wtf/go-nostr"
	"github.com/nbd-wtf/go-nostr/nip19"
)

const (
	// NIP-35: Torrent announcements
	KindTorrent = 2003
)

type Publisher struct {
	privateKey string
	publicKey  string
	relays     []string
}

type TorrentEventData struct {
	Title        string
	InfoHash     string
	FileName     string
	FileSize     int64
	MagnetLink   string
	WebSeedURL   string
	BlossomHash  string
	Description  string
	// Streaming extensions
	HLSPlaylistURL string
	StreamURL      string  
	Duration       int64   // seconds
	VideoWidth     int
	VideoHeight    int
	FrameRate      string
	VideoCodec     string
	MimeType       string
}

// NewPublisher creates a new Nostr publisher
func NewPublisher(privateKeyHex string, relays []string) (*Publisher, error) {
	if privateKeyHex == "" {
		// Generate a new key if none provided
		sk := nostr.GeneratePrivateKey()
		privateKeyHex = sk
	}

	// Validate private key
	privateKeyBytes, err := hex.DecodeString(privateKeyHex)
	if err != nil {
		return nil, fmt.Errorf("invalid private key hex: %w", err)
	}

	if len(privateKeyBytes) != 32 {
		return nil, fmt.Errorf("private key must be 32 bytes")
	}

	publicKey, err := nostr.GetPublicKey(privateKeyHex)
	if err != nil {
		return nil, fmt.Errorf("error deriving public key: %w", err)
	}

	if len(relays) == 0 {
		relays = []string{
			"wss://relay.damus.io",
			"wss://nos.lol",
			"wss://relay.nostr.band",
		}
	}

	return &Publisher{
		privateKey: privateKeyHex,
		publicKey:  publicKey,
		relays:     relays,
	}, nil
}

// CreateTorrentEvent creates a NIP-35 compliant torrent announcement event with streaming extensions
func (p *Publisher) CreateTorrentEvent(data TorrentEventData) (*nostr.Event, error) {
	event := &nostr.Event{
		Kind:      KindTorrent,
		CreatedAt: nostr.Now(),
		Content:   data.Description,
		Tags:      nostr.Tags{},
	}

	// Add required tags according to NIP-35
	if data.Title != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"title", data.Title})
	}

	// Required: BitTorrent info hash (x tag)
	if data.InfoHash != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"x", data.InfoHash})
	}

	// Required: File information with size (file tag)
	if data.FileName != "" && data.FileSize > 0 {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"file", data.FileName, strconv.FormatInt(data.FileSize, 10)})
	}

	// Standard NIP-35 tags
	if data.WebSeedURL != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"webseed", data.WebSeedURL})
	}

	if data.MagnetLink != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"magnet", data.MagnetLink})
	}

	// Blossom integration (custom extension)
	if data.BlossomHash != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"blossom", data.BlossomHash})
	}

	// Streaming extensions (proposed for NIP-35)
	if data.StreamURL != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"stream", data.StreamURL})
	}

	if data.HLSPlaylistURL != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"hls", data.HLSPlaylistURL})
	}

	if data.Duration > 0 {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"duration", strconv.FormatInt(data.Duration, 10)})
	}

	if data.VideoWidth > 0 && data.VideoHeight > 0 {
		resolution := fmt.Sprintf("%dx%d", data.VideoWidth, data.VideoHeight)
		if data.FrameRate != "" && data.VideoCodec != "" {
			event.Tags = event.Tags.AppendUnique(nostr.Tag{"video", resolution, data.FrameRate, data.VideoCodec})
		} else if data.FrameRate != "" {
			event.Tags = event.Tags.AppendUnique(nostr.Tag{"video", resolution, data.FrameRate})
		} else {
			event.Tags = event.Tags.AppendUnique(nostr.Tag{"video", resolution})
		}
	}

	if data.MimeType != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"m", data.MimeType})
	}

	// Content categorization tags
	event.Tags = event.Tags.AppendUnique(nostr.Tag{"t", "torrent"})
	event.Tags = event.Tags.AppendUnique(nostr.Tag{"t", "blossom"})
	
	// Determine content category based on mime type
	if data.MimeType != "" {
		if strings.HasPrefix(data.MimeType, "video/") {
			event.Tags = event.Tags.AppendUnique(nostr.Tag{"t", "video"})
			if data.StreamURL != "" || data.HLSPlaylistURL != "" {
				event.Tags = event.Tags.AppendUnique(nostr.Tag{"t", "streaming"})
			}
			// Add video category path
			event.Tags = event.Tags.AppendUnique(nostr.Tag{"tcat", "video,streaming"})
		} else if strings.HasPrefix(data.MimeType, "audio/") {
			event.Tags = event.Tags.AppendUnique(nostr.Tag{"t", "audio"})
			event.Tags = event.Tags.AppendUnique(nostr.Tag{"tcat", "audio"})
		}
	}

	// Sign the event
	err := event.Sign(p.privateKey)
	if err != nil {
		return nil, fmt.Errorf("error signing event: %w", err)
	}

	return event, nil
}

// PublishEvent publishes an event to configured relays
func (p *Publisher) PublishEvent(ctx context.Context, event *nostr.Event) error {
	if len(p.relays) == 0 {
		return fmt.Errorf("no relays configured")
	}

	successCount := 0
	errorCount := 0

	for _, relayURL := range p.relays {
		err := p.publishToRelay(ctx, relayURL, event)
		if err != nil {
			log.Printf("Failed to publish to relay %s: %v", relayURL, err)
			errorCount++
		} else {
			log.Printf("Successfully published to relay %s", relayURL)
			successCount++
		}
	}

	if successCount == 0 {
		return fmt.Errorf("failed to publish to any relay (%d errors)", errorCount)
	}

	log.Printf("Published to %d/%d relays successfully", successCount, len(p.relays))
	return nil
}

// publishToRelay publishes an event to a single relay
func (p *Publisher) publishToRelay(ctx context.Context, relayURL string, event *nostr.Event) error {
	relay, err := nostr.RelayConnect(ctx, relayURL)
	if err != nil {
		return fmt.Errorf("error connecting to relay: %w", err)
	}
	defer relay.Close()

	// Set a reasonable timeout
	publishCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	err = relay.Publish(publishCtx, *event)
	if err != nil {
		return fmt.Errorf("error publishing event: %w", err)
	}

	return nil
}

// PublishTorrentAnnouncement creates and publishes a NIP-35 torrent announcement
func (p *Publisher) PublishTorrentAnnouncement(ctx context.Context, data TorrentEventData) (*nostr.Event, error) {
	event, err := p.CreateTorrentEvent(data)
	if err != nil {
		return nil, fmt.Errorf("error creating torrent event: %w", err)
	}

	err = p.PublishEvent(ctx, event)
	if err != nil {
		return nil, fmt.Errorf("error publishing torrent event: %w", err)
	}

	return event, nil
}

// GetPublicKeyBech32 returns the public key in bech32 format (npub)
func (p *Publisher) GetPublicKeyBech32() (string, error) {
	return nip19.EncodePublicKey(p.publicKey)
}

// GetPrivateKeyBech32 returns the private key in bech32 format (nsec)
func (p *Publisher) GetPrivateKeyBech32() (string, error) {
	return nip19.EncodePrivateKey(p.privateKey)
}

// GetEventID returns the event ID in hex format
func GetEventID(event *nostr.Event) string {
	return event.ID
}

// GetEventIDBech32 returns the event ID in bech32 format (note)
func GetEventIDBech32(event *nostr.Event) (string, error) {
	return nip19.EncodeNote(event.ID)
}

// CreateMockPublisher creates a publisher that logs instead of publishing (for testing)
func CreateMockPublisher() *MockPublisher {
	sk := nostr.GeneratePrivateKey()
	pk, _ := nostr.GetPublicKey(sk)
	
	return &MockPublisher{
		privateKey: sk,
		publicKey:  pk,
		events:     make([]*nostr.Event, 0),
	}
}

// MockPublisher is a test implementation that doesn't actually publish
type MockPublisher struct {
	privateKey string
	publicKey  string
	events     []*nostr.Event
}

func (m *MockPublisher) CreateTorrentEvent(data TorrentEventData) (*nostr.Event, error) {
	event := &nostr.Event{
		Kind:      KindTorrent,
		CreatedAt: nostr.Now(),
		Content:   data.Description,
		Tags:      nostr.Tags{},
	}

	// Add required tags
	if data.Title != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"title", data.Title})
	}
	if data.InfoHash != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"x", data.InfoHash})
	}
	if data.FileName != "" && data.FileSize > 0 {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"file", data.FileName, strconv.FormatInt(data.FileSize, 10)})
	}
	if data.WebSeedURL != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"webseed", data.WebSeedURL})
	}
	if data.BlossomHash != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"blossom", data.BlossomHash})
	}
	if data.MagnetLink != "" {
		event.Tags = event.Tags.AppendUnique(nostr.Tag{"magnet", data.MagnetLink})
	}

	event.Tags = event.Tags.AppendUnique(nostr.Tag{"t", "torrent"})
	event.Tags = event.Tags.AppendUnique(nostr.Tag{"t", "blossom"})

	// Sign the event
	err := event.Sign(m.privateKey)
	if err != nil {
		return nil, fmt.Errorf("error signing event: %w", err)
	}

	return event, nil
}

func (m *MockPublisher) PublishTorrentAnnouncement(ctx context.Context, data TorrentEventData) (*nostr.Event, error) {
	event, err := m.CreateTorrentEvent(data)
	if err != nil {
		return nil, err
	}

	// Store event instead of publishing
	m.events = append(m.events, event)
	
	log.Printf("Mock: Would publish NIP-35 event (ID: %s) to relays", event.ID)
	log.Printf("Mock: Event content: %s", event.Content)
	log.Printf("Mock: Event tags: %v", event.Tags)
	
	return event, nil
}

func (m *MockPublisher) GetEvents() []*nostr.Event {
	return m.events
}