package main

import (
	"bufio"
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"time"

	"github.com/waku-org/go-waku/waku/v2/node"
	"github.com/waku-org/go-waku/waku/v2/protocol"
	"github.com/waku-org/go-waku/waku/v2/protocol/pb"
	"github.com/waku-org/go-waku/waku/v2/protocol/relay"
	"github.com/waku-org/go-waku/waku/v2/utils"
)

const sharedKey = "2b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfe"

type ChatMessage struct {
	Timestamp time.Time `json:"timestamp"`
	Nick      string    `json:"nick"`
	Message   string    `json:"message"`
	Key       string    `json:"key"`
}

func main() {
	ctx := context.Background()
	
	hostAddr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:0")
	
	wakuNode, err := node.New(
		node.WithHostAddress(hostAddr),
		node.WithWakuRelay(),
	)
	if err != nil {
		log.Fatal("Error creating waku node:", err)
	}

	if err := wakuNode.Start(ctx); err != nil {
		log.Fatal("Error starting waku node:", err)
	}
	defer wakuNode.Stop()

	pubsubTopic := protocol.DefaultPubsubTopic{}.String()
	contentTopic, _ := protocol.NewContentTopic("waku2chat", "1", "chat", "proto")

	contentFilter := protocol.NewContentFilter(pubsubTopic, contentTopic.String())
	subs, err := wakuNode.Relay().Subscribe(ctx, contentFilter)
	if err != nil {
		log.Fatal("Error subscribing to topic:", err)
	}
	
	if len(subs) == 0 {
		log.Fatal("No subscriptions created")
	}
	sub := subs[0]

	log.Println("Waku2 Chat Started")
	log.Println("Node ID:", wakuNode.ID())
	log.Println("Listening on:", wakuNode.ListenAddresses())
	log.Println("Shared Key:", sharedKey)
	
	var connected bool
	if len(wakuNode.Relay().PubSub().ListPeers(pubsubTopic)) == 0 {
		log.Println("No peers connected yet. Trying to connect to bootstrap nodes...")
		connected = connectToBootstrapNodes(ctx, wakuNode)
	} else {
		connected = true
	}

	if !connected {
		log.Fatal("Failed to connect to Waku network. Exiting.")
	}

	go receiveMessages(ctx, sub)

	reader := bufio.NewReader(os.Stdin)
	nick := fmt.Sprintf("user-%s", generateRandomID())
	
	fmt.Printf("Your nickname: %s\n", nick)
	fmt.Println("Type messages and press Enter to send (or 'quit' to exit):")
	
	for {
		fmt.Print("> ")
		text, _ := reader.ReadString('\n')
		text = strings.TrimSpace(text)
		
		if text == "quit" {
			break
		}
		
		if text == "" {
			continue
		}

		// Check if still connected before sending
		if len(wakuNode.Relay().PubSub().ListPeers(pubsubTopic)) == 0 {
			log.Println("Not connected to any peers. Message not sent.")
			continue
		}

		msg := ChatMessage{
			Timestamp: time.Now(),
			Nick:      nick,
			Message:   text,
			Key:       sharedKey,
		}
		
		msgBytes, err := json.Marshal(msg)
		if err != nil {
			log.Println("Error marshaling message:", err)
			continue
		}

		wakuMsg := &pb.WakuMessage{
			Payload:      msgBytes,
			ContentTopic: contentTopic.String(),
			Timestamp:    utils.GetUnixEpochFrom(wakuNode.Timesource().Now()),
		}

		_, err = wakuNode.Relay().Publish(ctx, wakuMsg)
		if err != nil {
			log.Println("Error publishing message:", err)
		}
	}
}

func receiveMessages(ctx context.Context, sub *relay.Subscription) {
	for {
		select {
		case <-ctx.Done():
			return
		case envelope := <-sub.Ch:
			if envelope.Message() == nil {
				continue
			}

			var msg ChatMessage
			err := json.Unmarshal(envelope.Message().Payload, &msg)
			if err != nil {
				continue
			}

			if msg.Key != sharedKey {
				continue
			}

			fmt.Printf("\n[%s] %s: %s\n> ", msg.Timestamp.Format("15:04:05"), msg.Nick, msg.Message)
		}
	}
}

func connectToBootstrapNodes(ctx context.Context, wakuNode *node.WakuNode) bool {
	bootstrapNodes := []string{
		// Latest Waku v2 production fleet nodes from waku.org
		"/dns4/node-01.do-ams3.waku.test.status.im/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ",
		"/dns4/node-01.gc-us-central1-a.waku.test.status.im/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS", 
		"/dns4/node-01.ac-cn-hongkong-c.waku.test.status.im/tcp/30303/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm",
		
		// Waku Sandbox nodes - actively maintained
		"/dns4/boot-01.do-ams3.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmPLtEfJpCyUM9JcVDhP3zJ1Mxx1L5WzXp5qJFH2rqVGJS",
		"/dns4/boot-01.gc-us-central1-a.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmQq5okTJWCUXo8F8Kf3kQ2kLPdqVUoJmqZ3cFWV3u8HuH",
		"/dns4/boot-01.ac-cn-hongkong-c.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmQJvn5BKvdXFPRTCNBVrWwAJ3dCLKxZDPtJFhLFpHQYoS",
		
		// Current production nodes with resolved IPs
		"/dns4/node-01.do-ams3.wakuv2.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e",
		"/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA", 
		"/dns4/node-01.ac-cn-hongkong-c.wakuv2.prod.statusim.net/tcp/30303/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD",
		
		// Use resolved IPs directly in case DNS is issue
		"/ip4/188.166.135.145/tcp/30303/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e",
		"/ip4/34.121.100.108/tcp/30303/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA",
		"/ip4/8.210.222.231/tcp/30303/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD",
	}

	connected := false
	for _, addr := range bootstrapNodes {
		// Create a context with 2 second timeout
		dialCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
		err := wakuNode.DialPeer(dialCtx, addr)
		cancel()
		
		if err != nil {
			log.Printf("Could not connect to bootstrap node: %v", err)
		} else {
			log.Printf("Connected to bootstrap node: %s", addr)
			connected = true
			break
		}
	}
	
	if !connected {
		log.Println("ERROR: Could not connect to any bootstrap nodes")
		log.Println("The program will not be able to send or receive messages.")
	}
	
	return connected
}

func generateRandomID() string {
	b := make([]byte, 4)
	rand.Read(b)
	return hex.EncodeToString(b)
}