package utils

import (
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"sync"

	"github.com/yl2chen/cidranger"
	ctrl "sigs.k8s.io/controller-runtime"
)

var AwsIPSvc = NewAwsIPService()

const (
	awsIPRangesUrl = "https://ip-ranges.amazonaws.com/ip-ranges.json"
)

type AwsIPRange struct {
	SyncToken  string          `json:"syncToken"`
	CreateDate string          `json:"createDate"`
	Prefixes   []AwsIPv4Prefix `json:"prefixes"`
	// IPv6Prefixes []AwsIPv6Prefix `json:"ipv6_prefixes"`
}

type AwsIPv4Prefix struct {
	IpPrefix string `json:"ip_prefix"`
	Region   string `json:"region"`
	Service  string `json:"service"`
	// NetworkBorderGroup string `json:"network_border_group"`
}
type AwsIPService struct {
	version string
	total   int
	ranger  cidranger.Ranger
	lock    sync.RWMutex
}

func NewAwsIPService() *AwsIPService {
	return &AwsIPService{
		ranger: cidranger.NewPCTrieRanger(),
	}
}

func (r *AwsIPService) Load() {
	logger := ctrl.Log.WithName("AwsIPService")
	ipRangs, err := getLatestAwsIPRangs()
	if err != nil {
		logger.Error(err, "failed to get aws ip ranges")
		return
	}
	if ipRangs.SyncToken == r.version {
		logger.Info("unchanged version, no need for updates, version=" + r.version)
		return
	}
	newRanger := cidranger.NewPCTrieRanger()
	for _, v := range ipRangs.Prefixes {
		_, network, err := net.ParseCIDR(v.IpPrefix)
		if err != nil {
			logger.Error(err, "aws ip ranges parsing failure, cidr="+v.IpPrefix)
			return
		}
		if err = newRanger.Insert(cidranger.NewBasicRangerEntry(*network)); err != nil {
			logger.Error(err, "aws ip ranges loading failure, cidr="+v.IpPrefix)
			return
		}
	}
	r.lock.Lock()
	r.version = ipRangs.SyncToken
	r.ranger = newRanger
	r.total = len(ipRangs.Prefixes)
	r.lock.Unlock()
	logger.Info(fmt.Sprintf("update successfully version=%s, create_date=%s, count=%v",
		r.version, ipRangs.CreateDate, r.total))
}

func (r *AwsIPService) Contains(ip string) bool {
	/* downgrade strategy start, allowd all */
	if r.total == 0 {
		return true
	}
	/* downgrade strategy end */

	r.lock.RLock()
	c, e := r.ranger.Contains(net.ParseIP(ip))
	if e != nil {
		c = false
	}
	r.lock.RUnlock()
	ctrl.Log.WithName("AwsIPService").Info(fmt.Sprintf("check valid aws ip=%s, result=%v", ip, c))
	return c
}

func getLatestAwsIPRangs() (*AwsIPRange, error) {
	client := http.Client{}
	req, err := http.NewRequest(http.MethodGet, awsIPRangesUrl, nil)
	if err != nil {
		return nil, err
	}
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	if res.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("invalid http status code %d", res.StatusCode)
	}
	defer res.Body.Close() //nolint:errcheck // skip error check

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	var resp AwsIPRange
	if err = json.Unmarshal(body, &resp); err != nil {
		return nil, err
	}
	return &resp, nil
}
