package main

//
// Generates IP and endpoint information.
//

import (
	"net"
	"net/url"
	"sort"
	"strings"

	"github.com/ooni/probe-cli/v3/internal/geoipx"
	"github.com/ooni/probe-cli/v3/internal/model"
	"github.com/ooni/probe-cli/v3/internal/netxlite"
)

// newIPInfo creates an IP to IPInfo mapping from addresses resolved
// by the probe (inside [creq]) or the TH (inside [addrs]).
func newIPInfo(creq *ctrlRequest, addrs []string) map[string]*model.THIPInfo {
	discoveredby := make(map[string]int64)
	for _, epnt := range creq.TCPConnect {
		addr, _, err := net.SplitHostPort(epnt)
		if err != nil || net.ParseIP(addr) == nil {
			continue
		}
		discoveredby[addr] |= model.THIPInfoFlagResolvedByProbe
	}
	for _, addr := range addrs {
		if net.ParseIP(addr) != nil {
			discoveredby[addr] |= model.THIPInfoFlagResolvedByTH
		}
	}
	ipinfo := make(map[string]*model.THIPInfo)
	for addr, flags := range discoveredby {
		if netxlite.IsBogon(addr) { // note: we already excluded non-IP addrs above
			flags |= model.THIPInfoFlagIsBogon
		}
		asn, _, _ := geoipx.LookupASN(addr) // AS0 on failure
		ipinfo[addr] = &model.THIPInfo{
			ASN:   int64(asn),
			Flags: flags,
		}
	}
	return ipinfo
}

// endpointInfo contains info about an endpoint to measure
type endpointInfo struct {
	// Addr is the address to measure
	Addr string

	// Epnt is the endpoint to measure
	Epnt string

	// TLS indicates whether we should try using TLS
	TLS bool
}

// ipInfoToEndpoints takes in input the [ipinfo] returned by newIPInfo
// and the [URL] provided by the probe to generate the list of endpoints
// to measure. We choose ports as follows:
//
// 1. if the input URL contains a port, we use such a port;
//
// 2. if the input URL scheme is "https", we choose port 443;
//
// 3. if the input URL scheme is "http", we use both 443 and 80, which
// allows us to include in the measurement information useful to determine
// whether an IP address is valid for a domain;
//
// 4. otherwise, we don't generate any endpoint to measure.
func ipInfoToEndpoints(URL *url.URL, ipinfo map[string]*model.THIPInfo) []endpointInfo {
	var ports []string
	if port := URL.Port(); port != "" {
		ports = []string{port} // as documented
	} else if URL.Scheme == "https" {
		ports = []string{"443"} // as documented
	} else if URL.Scheme == "http" {
		ports = []string{"80", "443"} // as documented
	}
	out := []endpointInfo{}
	for addr, info := range ipinfo {
		if (info.Flags & model.THIPInfoFlagIsBogon) != 0 {
			continue // as documented
		}
		for _, port := range ports {
			epnt := net.JoinHostPort(addr, port)
			out = append(out, endpointInfo{
				Addr: addr,
				Epnt: epnt,
				TLS:  port == "443",
			})
		}
	}
	// sort the output to make testing work deterministically since iterating
	// a map in golang isn't guaranteed to return ordered keys
	sort.SliceStable(out, func(i, j int) bool {
		return strings.Compare(out[i].Epnt, out[j].Epnt) < 0
	})
	return out
}
