package functions

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"

	"github.com/gorilla/websocket"
	"github.com/gravitl/netmaker/cli/config"
	"github.com/gravitl/netmaker/logger"
	"github.com/gravitl/netmaker/models"
)

func ssoLogin(endpoint string) string {
	var (
		authToken string
		interrupt = make(chan os.Signal, 1)
		url, _    = url.Parse(endpoint)
		socketURL = fmt.Sprintf("wss://%s/api/oauth/headless", url.Host)
	)
	signal.Notify(interrupt, os.Interrupt)
	conn, _, err := websocket.DefaultDialer.Dial(socketURL, nil)
	if err != nil {
		log.Fatal("error connecting to endpoint ", socketURL, err.Error())
	}
	defer conn.Close()
	_, msg, err := conn.ReadMessage()
	if err != nil {
		log.Fatal("error reading from server: ", err.Error())
	}
	fmt.Printf("Please visit:\n %s \n to authenticate\n", string(msg))
	done := make(chan struct{})
	defer close(done)
	go func() {
		for {
			msgType, msg, err := conn.ReadMessage()
			if err != nil {
				if msgType < 0 {
					done <- struct{}{}
					return
				}
				if !strings.Contains(err.Error(), "normal") {
					log.Fatal("read error: ", err.Error())
				}
				return
			}
			if msgType == websocket.CloseMessage {
				done <- struct{}{}
				return
			}
			if strings.Contains(string(msg), "JWT: ") {
				authToken = strings.TrimPrefix(string(msg), "JWT: ")
			} else {
				logger.Log(0, "Message from server:", string(msg))
				return
			}
		}
	}()
	for {
		select {
		case <-done:
			return authToken
		case <-interrupt:
			err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				logger.Log(0, "write close:", err.Error())
			}
			return authToken
		}
	}
}

func getAuthToken(ctx config.Context, force bool) string {
	if !force && ctx.AuthToken != "" {
		return ctx.AuthToken
	}
	if ctx.SSO {
		authToken := ssoLogin(ctx.Endpoint)
		config.SetAuthToken(authToken)
		return authToken
	}
	authParams := &models.UserAuthParams{UserName: ctx.Username, Password: ctx.Password}
	payload, err := json.Marshal(authParams)
	if err != nil {
		log.Fatal(err)
	}
	res, err := http.Post(ctx.Endpoint+"/api/users/adm/authenticate", "application/json", bytes.NewReader(payload))
	if err != nil {
		log.Fatal(err)
	}
	resBodyBytes, err := io.ReadAll(res.Body)
	if err != nil {
		log.Fatalf("Client could not read response body: %s", err)
	}
	if res.StatusCode != http.StatusOK {
		log.Fatalf("Error Status: %d Response: %s", res.StatusCode, string(resBodyBytes))
	}
	body := new(models.SuccessResponse)
	if err := json.Unmarshal(resBodyBytes, body); err != nil {
		log.Fatalf("Error unmarshalling JSON: %s", err)
	}
	authToken := body.Response.(map[string]any)["AuthToken"].(string)
	config.SetAuthToken(authToken)
	return authToken
}

func request[T any](method, route string, payload any) *T {
	var (
		_, ctx = config.GetCurrentContext()
		req    *http.Request
		err    error
	)
	if payload == nil {
		req, err = http.NewRequest(method, ctx.Endpoint+route, nil)
		if err != nil {
			log.Fatalf("Client could not create request: %s", err)
		}
	} else {
		payloadBytes, jsonErr := json.Marshal(payload)
		if jsonErr != nil {
			log.Fatalf("Error in request JSON marshalling: %s", err)
		}
		req, err = http.NewRequest(method, ctx.Endpoint+route, bytes.NewReader(payloadBytes))
		if err != nil {
			log.Fatalf("Client could not create request: %s", err)
		}
		req.Header.Set("Content-Type", "application/json")
	}
	if ctx.MasterKey != "" {
		req.Header.Set("Authorization", "Bearer "+ctx.MasterKey)
	} else {
		req.Header.Set("Authorization", "Bearer "+getAuthToken(ctx, false))
	}
	retried := false
retry:
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatalf("Client error making http request: %s", err)
	}
	// refresh JWT token
	if res.StatusCode == http.StatusUnauthorized && !retried && ctx.MasterKey == "" {
		req.Header.Set("Authorization", "Bearer "+getAuthToken(ctx, true))
		retried = true
		goto retry
	}
	resBodyBytes, err := io.ReadAll(res.Body)
	if err != nil {
		log.Fatalf("Client could not read response body: %s", err)
	}
	if res.StatusCode != http.StatusOK {
		log.Fatalf("Error Status: %d Response: %s", res.StatusCode, string(resBodyBytes))
	}
	body := new(T)
	if len(resBodyBytes) > 0 {
		if err := json.Unmarshal(resBodyBytes, body); err != nil {
			log.Fatalf("Error unmarshalling JSON: %s", err)
		}
	}
	return body
}

func get(route string) string {
	_, ctx := config.GetCurrentContext()
	req, err := http.NewRequest(http.MethodGet, ctx.Endpoint+route, nil)
	if err != nil {
		log.Fatal(err)
	}
	if ctx.MasterKey != "" {
		req.Header.Set("Authorization", "Bearer "+ctx.MasterKey)
	} else {
		req.Header.Set("Authorization", "Bearer "+getAuthToken(ctx, true))
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	bodyBytes, err := io.ReadAll(res.Body)
	if err != nil {
		log.Fatal(err)
	}
	return string(bodyBytes)
}
