package client

import (
	"fmt"
	"net"
	"net/url"
	"strings"
	"time"
)

// ConnectivityProbeReport is diagnostic-only reachability (not a C2 channel).
type ConnectivityProbeReport struct {
	C2Host       string   `json:"c2_host"`
	C2DNSOK      bool     `json:"c2_dns_ok"`
	C2DNSAddrs   []string `json:"c2_dns_addrs,omitempty"`
	C2TCPOK      bool     `json:"c2_tcp_ok"`
	C2TCPError   string   `json:"c2_tcp_error,omitempty"`
	PoolHost     string   `json:"pool_host,omitempty"`
	PoolDNSOK    bool     `json:"pool_dns_ok,omitempty"`
	PoolDNSAddrs []string `json:"pool_dns_addrs,omitempty"`
	PoolTCPOK    bool     `json:"pool_tcp_ok,omitempty"`
	PoolTCPError string   `json:"pool_tcp_error,omitempty"`
}

func runConnectivityProbe(serverURL, poolHost string, poolPort int) ConnectivityProbeReport {
	report := ConnectivityProbeReport{}
	host, port, err := hostPortFromServerURL(serverURL)
	if err != nil {
		report.C2Host = serverURL
		report.C2TCPError = err.Error()
		return report
	}
	report.C2Host = net.JoinHostPort(host, port)
	report.C2DNSOK, report.C2DNSAddrs = probeDNSResolve(host)
	report.C2TCPOK, report.C2TCPError = probeTCPConnect(host, port)

	if poolHost != "" {
		pport := poolPort
		if pport <= 0 {
			pport = 3333
		}
		pportStr := fmt.Sprintf("%d", pport)
		report.PoolHost = net.JoinHostPort(poolHost, pportStr)
		report.PoolDNSOK, report.PoolDNSAddrs = probeDNSResolve(poolHost)
		report.PoolTCPOK, report.PoolTCPError = probeTCPConnect(poolHost, pportStr)
	}
	return report
}

func hostPortFromServerURL(serverURL string) (host, port string, err error) {
	raw := strings.TrimSpace(serverURL)
	if raw == "" {
		return "", "", fmt.Errorf("empty server URL")
	}
	if !strings.Contains(raw, "://") {
		raw = "http://" + raw
	}
	u, err := url.Parse(raw)
	if err != nil {
		return "", "", err
	}
	host = u.Hostname()
	port = u.Port()
	if port == "" {
		if u.Scheme == "https" {
			port = "443"
		} else {
			port = "80"
		}
	}
	if host == "" {
		return "", "", fmt.Errorf("no host in server URL")
	}
	return host, port, nil
}

func probeDNSResolve(host string) (bool, []string) {
	addrs, err := net.LookupHost(host)
	if err != nil || len(addrs) == 0 {
		return false, nil
	}
	return true, addrs
}

func probeTCPConnect(host, port string) (bool, string) {
	addr := net.JoinHostPort(host, port)
	conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
	if err != nil {
		return false, err.Error()
	}
	_ = conn.Close()
	return true, ""
}
