package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

const (
	errTokenReq                  = "Error: paypal.go: Send token request"
	errTokenResp                 = "Error: paypal.go: Decode token response"
	errToken                     = "Error: paypal.go: Get access token"
	errCreateOrderReq            = "Error: paypal.go: Send CreateOrder request"
	errCreateOrderResp           = "Error: paypal.go: Decode CreateOrder response"
	errCaptureOrderToken         = "Error: paypal.go: Get access token for CaptureOrder"
	errCaptureOrderReq           = "Error: paypal.go: Send CaptureOrder request"
	errCaptureOrderResp          = "Error: paypal.go: Decode CaptureOrder response"
	errCaptureOrderDecodeCapture = "Error: paypal.go: Decode CaptureOrder capture"
	errCaptureOrderDecodeReceipt = "Error: paypal.go: Decode CaptureOrder receipt"
)

type Capture struct {
	ID            string `json:"id"`
	Status        string `json:"status"`
	PurchaseUnits []struct {
		Payments struct {
			Captures []struct {
				ID     string `json:"id"`
				Status string `json:"status"`
				Amount struct {
					CurrencyCode string `json:"currency_code"`
					Value        string `json:"value"`
				} `json:"amount"`
				CreateTime time.Time `json:"create_time"`
			} `json:"captures"`
		} `json:"payments"`
	} `json:"purchase_units"`
	Payer struct {
		Name struct {
			GivenName string `json:"given_name"`
			Surname   string `json:"surname"`
		} `json:"name"`
		EmailAddress string `json:"email_address"`
		Phone        struct {
			PhoneType   string `json:"phone_type"`
			PhoneNumber struct {
				NationalNumber string `json:"national_number"`
			} `json:"phone_number"`
		} `json:"phone"`
		Address struct {
			CountryCode string `json:"country_code"`
		} `json:"address"`
	} `json:"payer"`
}

type Receipt struct {
	PurchaseUnits []struct {
		Payments struct {
			Captures []struct {
				ID     string `json:"id"`
				Status string `json:"status"`
			} `json:"captures"`
		} `json:"payments"`
	} `json:"purchase_units"`
}

func Token() (string, error) {
	req, err := http.NewRequest("POST",
		os.Getenv("BASE_URL")+"/v1/oauth2/token",
		bytes.NewBufferString(`grant_type=client_credentials`))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.SetBasicAuth(os.Getenv("CLIENT_ID"), os.Getenv("CLIENT_SECRET"))

	raw, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("%s: %v", errTokenReq, err)
	}
	defer raw.Body.Close()

	var response struct {
		AccessToken string `json:"access_token"`
	}
	if err := json.NewDecoder(raw.Body).Decode(&response); err != nil {
		return "", fmt.Errorf("%s: %v", errTokenResp, err)
	}

	return response.AccessToken, nil
}

func CreateOrder() (string, error) {
	token, err := Token()
	if err != nil {
		return "", fmt.Errorf("%s: %v", errToken, err)
	}

	type Amount struct {
		CurrencyCode string `json:"currency_code"`
		Value        string `json:"value"`
	}
	type PurchaseUnits struct {
		Amount Amount `json:"amount"`
	}
	type Address struct {
		CountryCode string `json:"country_code"`
	}
	type Paypal struct {
		Address Address `json:"address"`
	}
	type PaymentSource struct {
		Paypal Paypal `json:"paypal"`
	}
	type ApplicationContext struct {
		ShippingPreference string `json:"shipping_preference"`
	}
	type Order struct {
		Intent             string             `json:"intent"`
		PurchaseUnits      []PurchaseUnits    `json:"purchase_units"`
		PaymentSource      PaymentSource      `json:"payment_source"`
		ApplicationContext ApplicationContext `json:"application_context"`
	}

	// This payload will fill out defaults in PayPal
	// checkout window (CR code, no shipping, etc)
	order := Order{
		Intent: "CAPTURE",
		PurchaseUnits: []PurchaseUnits{{Amount: Amount{
			CurrencyCode: "USD",
			Value:        os.Getenv("PRICE"),
		}}},
		PaymentSource: PaymentSource{Paypal: Paypal{Address: Address{
			CountryCode: "CR",
		}}},
		ApplicationContext: ApplicationContext{
			ShippingPreference: "NO_SHIPPING",
		},
	}

	payload, err := json.Marshal(order)
	req, err := http.NewRequest("POST",
		os.Getenv("BASE_URL")+"/v2/checkout/orders",
		bytes.NewBuffer(payload))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	raw, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("%s: %v", errCreateOrderReq, err)
	}
	defer raw.Body.Close()

	var response struct {
		ID string `json:"id"`
	}

	if err := json.NewDecoder(raw.Body).Decode(&response); err != nil {
		return "", fmt.Errorf("%s: %v", errCreateOrderResp, err)
	}

	return response.ID, nil
}

func CaptureOrder(orderID string) (Capture, Receipt, error) {
	token, err := Token()
	if err != nil {
		return Capture{}, Receipt{}, fmt.Errorf("%s: %v", errCaptureOrderToken, err)
	}

	req, err := http.NewRequest("POST",
		os.Getenv("BASE_URL")+"/v2/checkout/orders/"+orderID+"/capture",
		nil)
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	raw, err := http.DefaultClient.Do(req)
	if err != nil {
		return Capture{}, Receipt{}, fmt.Errorf("%s: %v", errCaptureOrderReq, err)
	}
	defer raw.Body.Close()

	var capture Capture
	var receipt Receipt

	body, err := io.ReadAll(raw.Body)
	if err != nil {
		return Capture{}, Receipt{}, fmt.Errorf("%s: %v", errCaptureOrderResp, err)
	}

	if err := json.Unmarshal(body, &capture); err != nil {
		return Capture{}, Receipt{}, fmt.Errorf("%s: %v", errCaptureOrderDecodeCapture, err)
	}

	if err := json.Unmarshal(body, &receipt); err != nil {
		return Capture{}, Receipt{}, fmt.Errorf("%s: %v", errCaptureOrderDecodeReceipt, err)
	}

	return capture, receipt, nil
}