package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"regexp"
	"strings"

	"github.com/google/uuid"
)

type AmazonBearer struct {
	CustomerId string `json:"customer_id"`
	Extensions struct {
		DeviceInfo struct {
			DeviceSN   string `json:"device_serial_number"`
			DeviceType string `json:"device_type"`
			DeviceName string `json:"device_name"`
		} `json:"device_info"`
		CustomerInfo struct {
			Name        string `json:"name"`
			HomeRegion  string `json:"home_region"`
			AccountPool string `json:"account_pool"`
			GivenName   string `json:"given_name"`
			UserId      string `json:"user_id"`
		} `json:"customer_info"`
	} `json:"extensions"`
	Tokens struct {
		MacDMS struct {
			ADPToken         string `json:"adp_token"`
			DevicePrivateKey string `json:"device_private_key"`
		} `json:"mac_dms"`
		StoreAuthCookie struct {
			Cookie string `json:"cookie"`
		} `json:"store_authentication_cookie"`
		Bearer struct {
			AccessToken  string `json:"access_token"`
			ExpiresIn    string `json:"expires_in"`
			RefreshToken string `json:"refresh_token"`
		} `json:"bearer"`
	} `json:"tokens"`
}

type AmazonApi struct {
	Payload struct {
		AuthData struct {
			Global string `json:"use_global_authentication"`
			User   struct {
				UserId   string `json:"user_id"`
				Password string `json:"password"`
			} `json:"user_id_password"`
			AccessToken string `json:"access_token,omitempty"`
		} `json:"auth_data"`
		RegistrationData struct {
			Domain          string `json:"domain"`
			Type            string `json:"device_type"`
			Serial          string `json:"device_serial"`
			AppName         string `json:"app_name"`
			AppVersion      string `json:"app_version"`
			Model           string `json:"device_model"`
			OSVersion       string `json:"os_version"`
			SoftwareVersion string `json:"software_version"`
		} `json:"registration_data"`
		RequestedToken []string `json:"requested_token_type"`
		Cookies        struct {
			Domain  string   `json:"domain"`
			Website []string `json:"website_cookies"`
		} `json:"cookies"`
		UserContext struct {
			Frc string `json:"frc"`
		} `json:"user_context_map"`
		DeviceMetadata DeviceMetadata `json:"device_metadata"`
		Extenstions    []string       `json:"requested_extensions"`
	} `json:"api_payload"`
	Headers struct {
		Login      string     `json:"login"`
		Refresh    string     `json:"refresh"`
		Main       string     `json:"main"`
		Instance   string     `json:"app_instance"`
		MapVersion MapVersion `json:"map_version"`
	} `json:"headers"`
}
type DeviceMetadata struct {
	DeviceOS     string `json:"device_os_family"`
	DeviceType   string `json:"device_type"`
	DeviceSerial string `json:"device_serial"`
	MacAddress   string `json:"mac_address"`
	IMEI         string `json:"imei"`
	Manufacturer string `json:"manufacturer"`
	Model        string `json:"model"`
	OSVersion    string `json:"os_version"`
	AndroidID    string `json:"android_id"`
	BuildSerial  string `json:"build_serial"`
	Product      string `json:"product"`
}
type MapVersion struct {
	Version  string `json:"current_version"`
	Package  string `json:"package_name"`
	Platform string `json:"platform"`
}
type RefreshPayload struct {
	AppName       string         `json:"app_name"`
	AppVersion    string         `json:"app_version"`
	SourceType    string         `json:"source_token_type"`
	Token         string         `json:"source_token"`
	RequestedType string         `json:"requested_token_type"`
	MetaData      DeviceMetadata `json:"device_metadata"`
	MapVersion    MapVersion     `json:"map_version"`
}

type LoginResponse struct {
	Response struct {
		Success   *AmazonBearer `json:"success"`
		Challenge *struct {
			Reason  string `json:"challenge_reason"`
			URI     string `json:"uri"`
			Method  string `json:"required_authentication_method"`
			Context string `json:"challenge_context"`
		} `json:"challenge"`
	} `json:"response"`
}

type CookiesResponse struct {
	Response struct {
		Tokens struct {
			Cookies struct {
				AmazonCom []struct {
					Path     string `json:"Path"`
					Secure   bool   `json:"Secure"`
					Value    string `json:"Value"`
					Expires  string `json:"Expires"`
					HTTPOnly bool   `json:"HttpOnly"`
					Name     string `json:"Name"`
				} `json:".amazon.com"`
			} `json:"cookies"`
		} `json:"tokens"`
		URI string `json:"uri"`
	} `json:"response"`
	RequestID string `json:"request_id"`
}

var (
	api       AmazonApi
	bearer    AmazonBearer
	logistics CookiesResponse
)

func login() {
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(api.Payload)
	client := &http.Client{}
	req, _ := http.NewRequest("POST", "https://api.amazon.com/auth/register", b)
	uuid, _ := uuid.NewRandom()
	req.Header.Add("X-Amzn-RequestId", uuid.String())
	req.Header.Add("x-amzn-identity-auth-domain", ".amazon.com")
	req.Header.Add("User-Agent", api.Headers.Login)
	req.Header.Add("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
	}
	var response LoginResponse
	defer resp.Body.Close()
	json.NewDecoder(resp.Body).Decode(&response)
	if response.Response.Success != nil {
		bearer = *response.Response.Success
		fmt.Println("Login Succeeded for Device: " + bearer.Extensions.DeviceInfo.DeviceName)
	} else if response.Response.Challenge != nil {
		scanner := bufio.NewScanner(os.Stdin)
		cookieJar, _ := cookiejar.New(nil)
		client2 := &http.Client{Jar: cookieJar}
		uri, _ := url.Parse(response.Response.Challenge.URI)
		data := uri.Query()
		data.Add("openid.pape.max_auth_age", "0")
		data.Add("openid.identity", "http://specs.openid.net/auth/2.0/identifier_select")
		data.Add("accountStatusPolicy", "P1")
		data.Add("language", "en_US")
		data.Add("openid.return_to", "https://www.amazon.com/ap/maplanding")
		data.Add("openid.assoc_handle", "amzn_device_na")
		data.Add("openid.oa2.response_type", "token")
		data.Add("openid.mode", "checkid_setup")
		data.Add("openid.ns.pape", "http://specs.openid.net/extensions/pape/1.0")
		data.Add("openid.ns.oa2", "http://www.amazon.com/ap/ext/oauth/2")
		data.Add("openid.oa2.scope", "device_auth_access")
		data.Add("openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select")
		data.Add("openid.oa2.client_id", "device:"+api.Payload.RegistrationData.Serial)
		data.Add("disableLoginPrepopulate", "0")
		data.Add("openid.ns", "http://specs.openid.net/auth/2.0")
		uri.RawQuery = data.Encode()
		req2, _ := http.NewRequest("GET", uri.String(), nil)
		req2.Header.Add("x-amzn-identity-auth-domain", ".amazon.com")
		req2.Header.Add("User-Agent", api.Headers.Login)
		resp2, err := client2.Do(req2)
		if err != nil {
			fmt.Println(err)
		}
		referer := resp2.Request.URL.String()
		fmt.Println(referer)
		defer resp2.Body.Close()
		body, err := ioutil.ReadAll(resp2.Body)
		if err != nil {
			fmt.Println(err)
		}
		if response.Response.Challenge.Reason == "MissingRequiredAuthenticationData" {
			if response.Response.Challenge.Method == "OTPCode" {
				client3 := &http.Client{Jar: cookieJar, CheckRedirect: func(req *http.Request, via []*http.Request) error {
					return http.ErrUseLastResponse
				}}
				fmt.Println("Account set for 2 Factor Authentication")
				fmt.Print("Enter One Time Code: ")
				scanner.Scan()
				otp := scanner.Text()
				re := regexp.MustCompile(`<input type="hidden" name="(.+?)" value="(.+?)"`)
				stuff := re.FindAllStringSubmatch(string(body), -1)
				data2 := url.Values{}
				for _, value := range stuff {
					data2.Add(value[1], value[2])
				}
				data2.Add("otpCode", otp)
				data2.Add("rememberDevice", "")
				req3, _ := http.NewRequest("POST", "https://www.amazon.com/ap/signin", strings.NewReader(data2.Encode()))
				req3.Header.Add("x-amzn-identity-auth-domain", ".amazon.com")
				req3.Header.Add("User-Agent", api.Headers.Login)
				req3.Header.Add("Content-Type", "application/x-www-form-urlencoded")
				resp3, err := client3.Do(req3)
				if err != nil {
					fmt.Println(err)
				}
				if resp3.StatusCode == 302 {
					redir := resp3.Header.Get("Location")
					if strings.Contains(redir, "maplanding") {
						fmt.Println("2FA Authentication Successful")
						redir_url, _ := url.Parse(redir)
						data3 := redir_url.Query()
						token := data3.Get("openid.oa2.access_token")
						api.Payload.AuthData.AccessToken = token
						login()
					}
				}
			} else {
				fmt.Println(response.Response.Challenge)
				fmt.Println("MissingRequiredAuthenticationData - Unknown Method")
			}
		} else if response.Response.Challenge.Reason == "HandleOnWebView" {
			fmt.Println("HandleOnWebView")
		} else if response.Response.Challenge.Reason == "AuthenticationFailed" {
			fmt.Println("Login Failed - Incorrect User/Pass")
		} else {
			fmt.Println(response.Response.Challenge)
			fmt.Println("Login Failed due to Challenge")
		}
	} else {
		fmt.Println("Login Failed for unknown reason")
		fmt.Println(resp.Status)
		io.Copy(os.Stdout, resp.Body)
	}
}

func log_cookies() {
	data := url.Values{}
	data.Set("requested_token_type", "auth_cookies")
	data.Set("app_name", api.Headers.MapVersion.Package)
	data.Set("app_version", "130050002")
	data.Set("domain", ".amazon.com")
	data.Set("source_token_type", "refresh_token")
	data.Set("source_token", bearer.Tokens.Bearer.RefreshToken)
	data.Set("url", "https://www.amazon.com/ap/signin?openid.return_to=https://logistics.amazon.com/onboarding/account/settings?version=V2")
	req, _ := http.NewRequest("POST", "https://www.amazon.com/ap/exchangetoken/cookies", strings.NewReader(data.Encode()))
	uuid, _ := uuid.NewRandom()
	req.Header.Add("X-Amzn-RequestId", uuid.String())
	req.Header.Add("x-amzn-identity-auth-domain", ".amazon.com")
	req.Header.Add("User-Agent", api.Headers.Refresh)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
	}

	defer resp.Body.Close()
	json.NewDecoder(resp.Body).Decode(&logistics)
	io.Copy(ioutil.Discard, resp.Body)
}
