package pairing

import (
	"io"
	"net/http"

	"go.uber.org/zap"

	"github.com/status-im/status-go/signal"
)

const (
	// Handler routes for pairing
	pairingBase                = "/pairing"
	pairingChallenge           = pairingBase + "/challenge"
	pairingSendAccount         = pairingBase + "/sendAccount"
	pairingReceiveAccount      = pairingBase + "/receiveAccount"
	pairingSendSyncDevice      = pairingBase + "/sendSyncDevice"
	pairingReceiveSyncDevice   = pairingBase + "/receiveSyncDevice"
	pairingSendInstallation    = pairingBase + "/sendInstallation"
	pairingReceiveInstallation = pairingBase + "/receiveInstallation"
)

// Account handling

func handleReceiveAccount(logger *zap.Logger, pr PayloadReceiver) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
		payload, err := io.ReadAll(r.Body)
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
			logger.Error("handleReceiveAccount io.ReadAll(r.Body)", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})

		err = pr.Receive(payload)
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingAccount})
			logger.Error("handleReceiveAccount pr.Receive(payload)", zap.Error(err), zap.Binary("payload", payload))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingAccount})
	}
}

func handleSendAccount(logger *zap.Logger, pm PayloadMounter, beforeSending func()) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
		w.Header().Set("Content-Type", "application/octet-stream")
		err := pm.Mount()
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
			logger.Error("handleSendAccount pm.Mount()", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}

		beforeSending()
		_, err = w.Write(pm.ToSend())
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
			logger.Error("handleSendAccount w.Write(pm.ToSend())", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})

		pm.LockPayload()
	}
}

// Device sync handling

func handleParingSyncDeviceReceive(logger *zap.Logger, pr PayloadReceiver) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice})
		payload, err := io.ReadAll(r.Body)
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
			logger.Error("handleParingSyncDeviceReceive io.ReadAll(r.Body)", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})

		err = pr.Receive(payload)
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionSyncDevice})
			logger.Error("handleParingSyncDeviceReceive pr.Receive(payload)", zap.Error(err), zap.Binary("payload", payload))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionSyncDevice})
	}
}

func handlePairingSyncDeviceSend(logger *zap.Logger, pm PayloadMounter, beforeSending func()) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice})
		w.Header().Set("Content-Type", "application/octet-stream")

		err := pm.Mount()
		if err != nil {
			// maybe better to use a new event type here instead of EventTransferError?
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
			logger.Error("handlePairingSyncDeviceSend pm.Mount()", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}

		beforeSending()
		_, err = w.Write(pm.ToSend())
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
			logger.Error("handlePairingSyncDeviceSend w.Write(pm.ToSend())", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})

		pm.LockPayload()
	}
}

// Installation data handling

func handleReceiveInstallation(logger *zap.Logger, pmr PayloadMounterReceiver) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingInstallation})
		payload, err := io.ReadAll(r.Body)
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
			logger.Error("handleReceiveInstallation io.ReadAll(r.Body)", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})

		err = pmr.Receive(payload)
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingInstallation})
			logger.Error("handleReceiveInstallation pmr.Receive(payload)", zap.Error(err), zap.Binary("payload", payload))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingInstallation})
	}
}

func handleSendInstallation(logger *zap.Logger, pmr PayloadMounterReceiver, beforeSending func()) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingInstallation})
		w.Header().Set("Content-Type", "application/octet-stream")
		err := pmr.Mount()
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
			logger.Error("handleSendInstallation pmr.Mount()", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}

		beforeSending()
		_, err = w.Write(pmr.ToSend())
		if err != nil {
			signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
			logger.Error("handleSendInstallation w.Write(pmr.ToSend())", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})

		pmr.LockPayload()
	}
}

// Challenge middleware and handling

func middlewareChallenge(cg *ChallengeGiver, next http.Handler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := cg.checkChallengeResponse(w, r)
		if err != nil {
			if cErr, ok := err.(*ChallengeError); ok {
				http.Error(w, cErr.Text, cErr.HTTPCode)
				return
			}
			cg.logger.Error("failed to checkChallengeResponse in middlewareChallenge", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}

		next.ServeHTTP(w, r)
	}
}

func handlePairingChallenge(cg *ChallengeGiver) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		challenge, err := cg.getChallenge(w, r)
		if err != nil {
			if cErr, ok := err.(*ChallengeError); ok {
				http.Error(w, cErr.Text, cErr.HTTPCode)
				return
			}
			cg.logger.Error("failed to getChallenge in handlePairingChallenge", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/octet-stream")
		_, err = w.Write(challenge)
		if err != nil {
			cg.logger.Error("failed to Write(challenge) in handlePairingChallenge", zap.Error(err))
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
	}
}