package main

import (
	"bytes"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"time"

	wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)

type LauncherAuth struct {
	Id    string
	Name  string
	Token string
}

type McProfile struct {
	Id   string
	Name string
}

type devcodeResp struct {
	User_code        string
	Device_code      string
	Verification_uri string
	Expires_in       string
	Interval         int
	Message          string
}

type authenticationResp struct {
	Access_token      string
	Token_type        string
	Refresh_token     string
	Expires_in        string
	Error             string
	Error_description string
}

type xboxAuthProperties struct {
	AuthMethod string
	SiteName   string
	RpsTicket  string
}

type xboxAuthRequest struct {
	Properties   xboxAuthProperties
	RelyingParty string
	TokenType    string
}

type xboxDisplayClaim struct {
	Uhs string
	Gtg string
	Xid string
	Agg string
	Usr string
	Utr string
	Prv string
}

type xboxDisplayClaims struct {
	Xui []xboxDisplayClaim
}

type xboxAuthResponse struct {
	IssueInstant  time.Time
	NotAfter      time.Time
	Token         string
	DisplayClaims xboxDisplayClaims
}

type XSTSProperties struct {
	SandboxId  string
	UserTokens []string
}

type XSTSRequest struct {
	Properties   XSTSProperties
	RelyingParty string
	TokenType    string
}

type McAuthRequest struct {
	Xtoken   string `json:"xtoken"`
	Platform string `json:"platform"`
}

type McAuthResponse struct {
	Username     string
	Access_token string
	Expires_in   string
	Token_type   string
}

func getHTTPRedirect(w http.ResponseWriter, r *http.Request, srv *http.Server, code *string) {
	r.ParseForm()
	fmt.Printf("Response Code: %s\n", r.Form.Get("code"))
	if r.Form.Get("code") != "" {
		*code = r.Form.Get("code")
		io.WriteString(w, "You can now close this window and return to the application.")
	} else {
		srv.Shutdown(r.Context())
	}
}

func AuthCode(a App) (authenticationResp, error) {
	authentication := authenticationResp{}
	resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", url.Values{
		"client_id": {client_id},
		"scope":     {"XboxLive.SignIn XboxLive.offline_access"},
	})
	if err != nil {
		return authentication, fmt.Errorf("Unable to request device code: %e\n", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return authentication, fmt.Errorf("Unable to request device code: %s\n", resp.Status)
	}
	data, _ := io.ReadAll(resp.Body)
	codeResp := devcodeResp{}
	json.Unmarshal(data, &codeResp)
	//display message
	fmt.Printf("resp: %s\n", data)
	openbrowser(codeResp.Verification_uri)
	wruntime.MessageDialog(a.Ctx, wruntime.MessageDialogOptions{Type: wruntime.InfoDialog, Title: "Authentication", Message: codeResp.Message + ". The code has been automatically coppied to the clipboard."})
	wruntime.ClipboardSetText(a.Ctx, codeResp.Device_code)
	ticker := time.NewTicker(time.Second * time.Duration(codeResp.Interval))
	for range ticker.C {
		resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", url.Values{
			"client_id":   {client_id},
			"grant_type":  {"urn:ietf:params:oauth:grant-type:device_code"},
			"device_code": {codeResp.Device_code},
		})
		if err != nil {
			return authentication, fmt.Errorf("Authentication request error %e\n", err)
		}
		defer resp.Body.Close()
		data, _ := io.ReadAll(resp.Body)
		json.Unmarshal(data, &authentication)
		if authentication.Error == "" {
			return authentication, nil
		}
	}
	return authentication, fmt.Errorf("Unknown error")
}

func OAuth2(a App) (authenticationResp, error) {
	code := "code"
	srv := http.Server{Addr: ":5000"}
	authentication := authenticationResp{}
	verifier := make([]byte, 128)
	rand.Read(verifier)
	verifier_string := base64.RawURLEncoding.EncodeToString(verifier)
	challenge := sha256.Sum256([]byte(verifier_string))
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { getHTTPRedirect(w, r, &srv, &code) })
	openbrowser("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=" + client_id + "&response_type=code&redirect_uri=http%3A%2F%2F127.0.0.1%3A5000&response_mode=query&scope=XboxLive.signin&state=12345&code_challenge=" + base64.RawURLEncoding.EncodeToString(challenge[:]) + "&code_challenge_method=S256&prompt=login")
	srv.ListenAndServe()
	fmt.Printf("continuing auth\n")
	resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", url.Values{
		"grant_type":    {"authorization_code"},
		"code":          {code},
		"redirect_uri":  {"http://127.0.0.1:5000"},
		"code_verifier": {verifier_string},
		"client_id":     {client_id},
	})
	if err != nil {
		return authenticationResp{}, fmt.Errorf("unable to request token: %e", err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	json.Unmarshal(data, &authentication)
	//fmt.Printf("auth data: %s\n", data)
	return authentication, nil
}

func TokenRefresh(app App, auth authenticationResp) (authenticationResp, error) {
	resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", url.Values{
		"client_id":     {client_id},
		"grant_type":    {"refresh_token"},
		"refresh_token": {auth.Refresh_token},
		"scope":         {"XboxLive.SignIn XboxLive.offline_access"},
	})
	if err != nil {
		return authenticationResp{}, fmt.Errorf("unable to refresh token: %e\n", err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	authResp := authenticationResp{}
	json.Unmarshal(data, &authResp)
	if authResp.Error != "" {
		return authResp, fmt.Errorf("unable to request new token: %s", authResp.Error_description)
	}
	return authResp, nil
}

func MicrosoftAuth(auth authenticationResp) (LauncherAuth, error) {
	//Xbox Live Auth
	req, _ := json.Marshal(xboxAuthRequest{Properties: xboxAuthProperties{AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com", RpsTicket: "d=" + auth.Access_token}, RelyingParty: "http://auth.xboxlive.com", TokenType: "JWT"})
	client := http.Client{}
	httpreq, _ := http.NewRequest("POST", "https://user.auth.xboxlive.com/user/authenticate", bytes.NewBuffer(req))
	httpreq.Header.Add("x-xbl-contract-version", "1")
	httpreq.Header.Add("Content-Type", "application/json")
	httpreq.Header.Add("Accept", "application/json")
	httpResp, err := client.Do(httpreq)
	if err != nil {
		return LauncherAuth{}, fmt.Errorf("unable to obtain xbox live token: %e\n", err)
	}
	defer httpResp.Body.Close()
	if httpResp.StatusCode != 200 {
		return LauncherAuth{}, fmt.Errorf("unable to obtain xbox live token: %s\n", httpResp.Status)
	}
	d, _ := io.ReadAll(httpResp.Body)
	xblAuth := xboxAuthResponse{}
	json.Unmarshal(d, &xblAuth)
	xstsData, _ := json.Marshal(XSTSRequest{Properties: XSTSProperties{SandboxId: "RETAIL", UserTokens: []string{xblAuth.Token}}, RelyingParty: "rp://api.minecraftservices.com/", TokenType: "JWT"})
	httpXstsReq, _ := http.NewRequest("POST", "https://xsts.auth.xboxlive.com/xsts/authorize", bytes.NewBuffer(xstsData))
	httpXstsReq.Header.Add("Content-Type", "application/json")
	httpResp, err = client.Do(httpXstsReq)
	if err != nil {
		return LauncherAuth{}, fmt.Errorf("unable to obtain minecraft sts token: %e\n", err)
	}
	defer httpResp.Body.Close()
	if httpResp.StatusCode != 200 {
		return LauncherAuth{}, fmt.Errorf("unable to obtain minecraft sts token: %s\n", httpResp.Status)
	}
	d, _ = io.ReadAll(httpResp.Body)
	mcApi := xboxAuthResponse{}
	json.Unmarshal(d, &mcApi)
	mcAuthData, _ := json.Marshal(McAuthRequest{Xtoken: "XBL 3.0 x=" + mcApi.DisplayClaims.Xui[0].Uhs + ";" + mcApi.Token, Platform: "PC_LAUNCHER"})
	httpReqMC, _ := http.NewRequest("POST", "https://api.minecraftservices.com/launcher/login", bytes.NewBuffer(mcAuthData))
	httpReqMC.Header.Add("Content-Type", "application/json")
	httpReqMC.Header.Add("Accept", "application/json")
	resp, err := client.Do(httpReqMC)
	if err != nil {
		return LauncherAuth{}, fmt.Errorf("unable to obtain mojang auth token: %e\n", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return LauncherAuth{}, fmt.Errorf("unable to obtain mojang auth token: %s\n", resp.Status)
	}
	d, _ = io.ReadAll(resp.Body)
	mcAuth := McAuthResponse{}
	json.Unmarshal(d, &mcAuth)
	httpreq, err = http.NewRequest("GET", "https://api.minecraftservices.com/minecraft/profile", new(bytes.Buffer))
	httpreq.Header.Add("Content-Type", "application/json")
	httpreq.Header.Add("Accept", "application/json")
	httpreq.Header.Add("Authorization", "Bearer "+mcAuth.Access_token)
	resp, _ = client.Do(httpreq)
	if err != nil {
		return LauncherAuth{}, fmt.Errorf("unable to get profile data: %e\n", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return LauncherAuth{}, fmt.Errorf("unable to get profile data: %s\n", resp.Status)
	}
	data, _ := io.ReadAll(resp.Body)
	profile := McProfile{}
	json.Unmarshal(data, &profile)
	return LauncherAuth{Id: profile.Id, Name: profile.Name, Token: mcAuth.Access_token}, nil
}
