package main

import (
	"context"
	"encoding/xml"
	"errors"
	"fmt"
	"gosrc.io/xmpp"
	"gosrc.io/xmpp/stanza"
	"log"
	"time"
)

const (
	userJID       = "testuser2@localhost"
	serverAddress = "localhost:5222"
	nodeName      = "lel_node"
	serviceName   = "pubsub.localhost"
)

var invalidResp = errors.New("invalid response")

func main() {

	config := xmpp.Config{
		TransportConfiguration: xmpp.TransportConfiguration{
			Address: serverAddress,
		},
		Jid:        userJID,
		Credential: xmpp.Password("pass123"),
		// StreamLogger: os.Stdout,
		Insecure: true,
	}
	router := xmpp.NewRouter()
	router.NewRoute().Packet("message").
		HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
			data, _ := xml.Marshal(p)
			log.Println("Received a message ! => \n" + string(data))
		})

	client, err := xmpp.NewClient(&config, router, func(err error) { log.Println(err) })
	if err != nil {
		log.Fatalf("%+v", err)
	}

	// ==========================
	// Client connection
	err = client.Connect()
	if err != nil {
		log.Fatalf("%+v", err)
	}

	// ==========================
	// Create a node
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	createNode(ctx, cancel, client)

	// ================================================================================
	// Configure the node. This can also be done in a single message with the creation
	configureNode(ctx, cancel, client)

	// ====================================
	// Subscribe to this node :
	subToNode(ctx, cancel, client)

	// ==========================
	// Publish to that node
	pubToNode(ctx, cancel, client)

	// =============================
	// Let's purge the node :
	purgeRq, _ := stanza.NewPurgeAllItems(serviceName, nodeName)
	purgeCh, err := client.SendIQ(ctx, purgeRq)
	if err != nil {
		log.Fatalf("could not send purge request: %v", err)
	}
	select {
	case purgeResp := <-purgeCh:

		if purgeResp.Type == stanza.IQTypeError {
			cancel()
			if vld, err := purgeResp.IsValid(); !vld {
				log.Fatalf(invalidResp.Error()+" %v"+" reason: %v", purgeResp, err)
			}
			log.Fatalf("error while purging node : %s", purgeResp.Error.Text)
		}
		log.Println("node successfully purged")
	case <-time.After(1000 * time.Millisecond):
		cancel()
		log.Fatal("No iq response was received in time while purging node")
	}

	cancel()
}

func createNode(ctx context.Context, cancel context.CancelFunc, client *xmpp.Client) {
	rqCreate, err := stanza.NewCreateNode(serviceName, nodeName)
	if err != nil {
		log.Fatalf("%+v", err)
	}
	createCh, err := client.SendIQ(ctx, rqCreate)
	if err != nil {
		log.Fatalf("%+v", err)
	} else {

		if createCh != nil {
			select {
			case respCr := <-createCh:
				// Got response from server
				if respCr.Type == stanza.IQTypeError {
					if vld, err := respCr.IsValid(); !vld {
						log.Fatalf(invalidResp.Error()+" %+v"+" reason: %s", respCr, err)
					}
					if respCr.Error.Reason != "conflict" {
						log.Fatalf("%+v", respCr.Error.Text)
					}
					log.Println(respCr.Error.Text)
				} else {
					fmt.Print("successfully created channel")
				}
			case <-time.After(100 * time.Millisecond):
				cancel()
				log.Fatal("No iq response was received in time while creating node")
			}
		}
	}
}

func configureNode(ctx context.Context, cancel context.CancelFunc, client *xmpp.Client) {
	// First, ask for a form with the config options
	confRq, _ := stanza.NewConfigureNode(serviceName, nodeName)
	confReqCh, err := client.SendIQ(ctx, confRq)
	if err != nil {
		log.Fatalf("could not send iq : %v", err)
	}
	select {
	case confForm := <-confReqCh:
		// If the request was successful, we now have a form with configuration options to update
		fields, err := confForm.GetFormFields()
		if err != nil {
			log.Fatal("No config fields found !")
		}

		// These are some common fields expected to be present. Change processing to your liking
		if fields["pubsub#max_payload_size"] != nil {
			fields["pubsub#max_payload_size"].ValuesList[0] = "100000"
		}

		if fields["pubsub#notification_type"] != nil {
			fields["pubsub#notification_type"].ValuesList[0] = "headline"
		}

		// Send the modified fields as a form
		submitConf, err := stanza.NewFormSubmissionOwner(serviceName,
			nodeName,
			[]*stanza.Field{
				fields["pubsub#max_payload_size"],
				fields["pubsub#notification_type"],
			})

		c, _ := client.SendIQ(ctx, submitConf)
		select {
		case confResp := <-c:
			if confResp.Type == stanza.IQTypeError {
				cancel()
				if vld, err := confResp.IsValid(); !vld {
					log.Fatalf(invalidResp.Error()+" %v"+" reason: %v", confResp, err)
				}
				log.Fatalf("node configuration failed : %s", confResp.Error.Text)
			}
			log.Println("node configuration was successful")
			return

		case <-time.After(300 * time.Millisecond):
			cancel()
			log.Fatal("No iq response was received in time while configuring the node")
		}

	case <-time.After(300 * time.Millisecond):
		cancel()
		log.Fatal("No iq response was received in time while asking for the config form")
	}
}

func subToNode(ctx context.Context, cancel context.CancelFunc, client *xmpp.Client) {
	rqSubscribe, err := stanza.NewSubRq(serviceName, stanza.SubInfo{
		Node: nodeName,
		Jid:  userJID,
	})
	if err != nil {
		log.Fatalf("%+v", err)
	}
	subRespCh, _ := client.SendIQ(ctx, rqSubscribe)
	if subRespCh != nil {
		select {
		case <-subRespCh:
			log.Println("Subscribed to the service")
		case <-time.After(300 * time.Millisecond):
			cancel()
			log.Fatal("No iq response was received in time while subscribing")
		}
	}
}

func pubToNode(ctx context.Context, cancel context.CancelFunc, client *xmpp.Client) {
	pub, err := stanza.NewPublishItemRq(serviceName, nodeName, "", stanza.Item{
		Publisher: "testuser2",
		Any: &stanza.Node{
			XMLName: xml.Name{
				Space: "http://www.w3.org/2005/Atom",
				Local: "entry",
			},
			Nodes: []stanza.Node{
				{
					XMLName: xml.Name{Space: "", Local: "title"},
					Attrs:   nil,
					Content: "My pub item title",
					Nodes:   nil,
				},
				{
					XMLName: xml.Name{Space: "", Local: "summary"},
					Attrs:   nil,
					Content: "My pub item content summary",
					Nodes:   nil,
				},
				{
					XMLName: xml.Name{Space: "", Local: "link"},
					Attrs: []xml.Attr{
						{
							Name:  xml.Name{Space: "", Local: "rel"},
							Value: "alternate",
						},
						{
							Name:  xml.Name{Space: "", Local: "type"},
							Value: "text/html",
						},
						{
							Name:  xml.Name{Space: "", Local: "href"},
							Value: "http://denmark.lit/2003/12/13/atom03",
						},
					},
				},
				{
					XMLName: xml.Name{Space: "", Local: "id"},
					Attrs:   nil,
					Content: "My pub item content ID",
					Nodes:   nil,
				},
				{
					XMLName: xml.Name{Space: "", Local: "published"},
					Attrs:   nil,
					Content: "2003-12-13T18:30:02Z",
					Nodes:   nil,
				},
				{
					XMLName: xml.Name{Space: "", Local: "updated"},
					Attrs:   nil,
					Content: "2003-12-13T18:30:02Z",
					Nodes:   nil,
				},
			},
		},
	})

	if err != nil {
		log.Fatalf("%+v", err)
	}
	pubRespCh, _ := client.SendIQ(ctx, pub)
	if pubRespCh != nil {
		select {
		case <-pubRespCh:
			log.Println("Published item to the service")
		case <-time.After(300 * time.Millisecond):
			cancel()
			log.Fatal("No iq response was received in time while publishing")
		}
	}
}