package main

import (
	"context"
	"encoding/json"
	"os"
	"os/signal"
	"syscall"

	"github.com/status-im/status-go/protocol/common"
	"github.com/status-im/status-go/protocol/protobuf"
	msignal "github.com/status-im/status-go/signal"

	"github.com/urfave/cli/v2"
	"go.uber.org/zap"
)

func serve(cCtx *cli.Context) error {
	name := cCtx.String(NameFlag)
	port := cCtx.Int(PortFlag)
	apiModules := cCtx.String(APIModulesFlag)
	telemetryUrl := cCtx.String(TelemetryServerURLFlag)
	interactive := cCtx.Bool(InteractiveFlag)
	dest := cCtx.String(AddFlag)
	keyUID := cCtx.String(KeyUIDFlag)
	isDebugLevel := cCtx.Bool(DebugLevel)
	fleet := cCtx.String(FleetFlag)
	cmdName := cCtx.Command.Name

	logger, err := getSLogger(isDebugLevel)
	if err != nil {
		zap.S().Fatalf("Error initializing logger: %v", err)
	}
	logger.Infof("Running %v command, with:\n%v", cmdName, flagsUsed(cCtx))

	logger = logger.Named(name)

	cli, err := start(StartParams{
		Name:         name,
		Port:         port,
		APIModules:   apiModules,
		TelemetryURL: telemetryUrl,
		KeyUID:       keyUID,
		Fleet:        fleet,
	}, logger)
	if err != nil {
		return err
	}
	defer cli.stop()

	// Using the mobile signal handler to listen for received messages
	// because if we call messenger.RetrieveAll() from different routines we will miss messages in one of them
	// and the retrieve messages loop is started when starting a node, so we needed a different appproach,
	// alternatively we could have implemented another notification mechanism in the messenger, but this signal is already in place
	msignal.SetMobileSignalHandler(msignal.MobileSignalHandler(func(s []byte) {
		var evt EventType
		if err := json.Unmarshal(s, &evt); err != nil {
			logger.Error("unmarshaling event type", zap.Error(err), zap.String("event", string(s)))
			return
		}

		switch evt.Type {
		case msignal.EventNewMessages:
			var ev EventNewMessages
			if err := json.Unmarshal(evt.Event, &ev); err != nil {
				logger.Error("unmarshaling new message event", zap.Error(err), zap.Any("event", evt.Event))
				return
			}
			for _, message := range ev.Messages {
				logger.Infof("message received: %v (ID=%v)", message.Text, message.ID)
				// if request contact, accept it
				if message.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_SENT {
					if err := cli.sendContactRequestAcceptance(cCtx.Context, message.ID); err != nil {
						logger.Errorf("accepting contact request: %v", err)
						return
					}
				}
			}
		case "local-notifications":
			var ev LocalNotification
			if err := json.Unmarshal(evt.Event, &ev); err != nil {
				logger.Error("unmarshaling local notification event", zap.Error(err), zap.Any("event", evt.Event))
				return
			}
			logger.Infof("local notification: %v, title: %v, id: %v", ev.Category, ev.Title, ev.ID)
		default:
			logger.Debugf("received event type '%v'\t%v", evt.Type, string(evt.Event))
		}
	}))

	// Send contact request
	if dest != "" {
		err := cli.sendContactRequest(cCtx.Context, dest)
		if err != nil {
			return err
		}
	}

	// nightly testrunner looks for this log to consider node as started
	logger.Info("retrieve messages...")

	if interactive {
		ctx, cancel := context.WithCancel(cCtx.Context)
		go func() {
			waitForSigExit()
			cancel()
		}()
		interactiveSendMessageLoop(ctx, cli)
	} else {
		waitForSigExit()
	}

	logger.Info("Exiting")

	return nil
}

type EventType struct {
	Type  string          `json:"type"`
	Event json.RawMessage `json:"event"`
}

type EventNewMessages struct {
	Messages []*common.Message `json:"messages"`
}

type LocalNotification struct {
	ID       string `json:"id"`
	Title    string `json:"title"`
	Category string `json:"category"`
}

func waitForSigExit() {
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	<-sig
}