package ip_utils

import (
	"fmt"
	"github.com/aws/aws-lambda-go/events"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/handler_utils"
	"net"
	"os"
	"strings"
)

// ====================================================================================================
// region Payfast
// ====================================================================================================

// GetPayFastIPs returns a slice of string containing the current IP addresses
// associated with host names published by PayFast.
func getPayFastIPs() []string {
	hostnames := []string{
		"www.payfast.co.za",
		"w1w.payfast.co.za",
		"w2w.payfast.co.za",
		"sandbox.payfast.co.za",
		"api.payfast.co.za",
		"ips.payfast.co.za",
	}

	ips := []string{}

	for _, v := range hostnames {
		addresses, err := net.LookupIP(v)
		if err != nil {
			// Swallow and continue.
			continue
		}

		for _, a := range addresses {
			ips = append(ips, a.String())
		}
	}

	return ips
}

func VerifyPayfastSourceIP(sourceIP string) bool {
	ips := getPayFastIPs()
	for _, v := range ips {
		if v == sourceIP {
			return true
		}
	}

	// If we got here, it means the lookup failed, so let's check the IP ranges as defined by Payfast
	// Define the list of IP ranges as CIDR blocks - this is obtained from Payfast's documentation
	ipRanges := []string{
		"197.97.145.144/28",
		"41.74.179.192/27",
		"102.216.36.0/28",
		"102.216.36.128/28",
		"144.126.193.139/32",
	}

	// Now verify
	if isValidIP(sourceIP, ipRanges) {
		return true
	}

	return false
}

// endregion Payfast

// ====================================================================================================
// region Bob Pay
// ====================================================================================================

const (
	bobPayDevIP     = "13.245.84.126"
	bobPaySandboxIP = "13.246.115.225"
	bobPayProdIP    = "13.246.100.25"
)

func VerifyBobPaySourceIP(sourceIP string) bool {
	env := os.Getenv("ENVIRONMENT")

	switch env {
	case "dev":
		return sourceIP == bobPayDevIP
	case "stage":
		return sourceIP == bobPaySandboxIP
	case "prod":
		return sourceIP == bobPayProdIP
	}

	return false
}

// endregion Bob Pay

// ====================================================================================================
// region Cloudflare
// ====================================================================================================

func VerifyCloudflareSourceIP(sourceIP string) bool {
	ipRanges := []string{
		"173.245.48.0/20",
		"103.21.244.0/22",
		"103.22.200.0/22",
		"103.31.4.0/22",
		"141.101.64.0/18",
		"108.162.192.0/18",
		"190.93.240.0/20",
		"188.114.96.0/20",
		"197.234.240.0/22",
		"198.41.128.0/17",
		"162.158.0.0/15",
		"104.16.0.0/13",
		"104.24.0.0/14",
		"172.64.0.0/13",
		"131.0.72.0/22",
	}

	// Now verify
	if isValidIP(sourceIP, ipRanges) {
		return true
	}

	return false
}

// endregion Cloudflare

// ====================================================================================================
// region Helpers
// ====================================================================================================

func isValidIP(ipStr string, ipRanges []string) bool {
	ip := net.ParseIP(ipStr)
	if ip == nil {
		return false
	}

	for _, cidr := range ipRanges {
		_, subnet, err := net.ParseCIDR(cidr)
		if err != nil {
			fmt.Println("Error parsing CIDR:", err)
			continue
		}

		if subnet.Contains(ip) {
			return true
		}
	}

	return false
}

func ValidateIPAddress(ipAddress string) (cleanedIPAddress string, err error) {
	ipAddress = strings.ToLower(strings.TrimSpace(ipAddress))
	ip := net.ParseIP(ipAddress)
	if ip == nil {
		return "", errors.Error("invalid IP address")
	}
	return ipAddress, nil
}

func GetRequestSourceIP(proxyRequest *events.APIGatewayProxyRequest, websocketRequest *events.APIGatewayWebsocketProxyRequest) string {
	var requestSourceIP string
	if proxyRequest != nil {
		requestSourceIP = proxyRequest.RequestContext.Identity.SourceIP
		// Cloudflare uses this header to pass the real IP
		forwardedForHeader := handler_utils.FindHeaderValue(proxyRequest.Headers, "x-forwarded-for")
		if forwardedForHeader != "" &&
			VerifyCloudflareSourceIP(requestSourceIP) {
			forwardedForHeaderIPs := strings.Split(forwardedForHeader, ",")

			if len(forwardedForHeaderIPs) > 1 {
				// Use the second-to-last IP – the last IP will be the Cloudflare proxy's IP
				headerSourceIP := strings.TrimSpace(forwardedForHeaderIPs[len(forwardedForHeaderIPs)-2])
				return headerSourceIP
			}
		}
	} else if websocketRequest != nil {
		requestSourceIP = websocketRequest.RequestContext.Identity.SourceIP
	}

	return requestSourceIP
}

// endregion Helpers