package infra

import (
	"fmt"
	"net"
	"strings"
	"sync"
	"time"

	"arcline-audit/internal/types"
)

// CDN ranges for common CDN providers (simplified detection based on IP prefixes).
var cdnRanges = map[string][]string{
	"Cloudflare": {
		"104.16.", "104.17.", "104.18.", "104.19.", "104.20.", "104.21.",
		"104.22.", "104.23.", "104.24.", "104.25.", "104.26.", "104.27.",
		"104.28.", "104.29.", "104.30.", "104.31.",
		"172.64.", "172.65.", "172.66.", "172.67.", "172.68.", "172.69.",
		"172.70.", "172.71.",
	},
	"Fastly": {
		"151.101.", "199.232.", "23.235.", "146.75.",
	},
	"Amazon CloudFront": {
		"13.32.", "13.33.", "13.224.", "13.225.", "13.226.", "13.227.",
		"13.249.", "54.192.", "54.230.", "54.239.",
	},
}

// commonPorts are the ports to probe.
var commonPorts = map[int]string{
	80:   "HTTP",
	443:  "HTTPS",
	22:   "SSH",
	3306: "MySQL",
	5432: "PostgreSQL",
}

// Run performs all infrastructure checks for the given domain.
// It accepts pre-resolved IPs to avoid redundant lookups; if empty, it resolves the domain itself.
func Run(domain string, resolvedIPs []string) types.InfraResult {
	var result types.InfraResult

	ips := resolvedIPs
	if len(ips) == 0 {
		var err error
		ips, err = net.LookupHost(domain)
		if err != nil || len(ips) == 0 {
			result.Checks = append(result.Checks, types.CheckResult{
				Status: types.StatusFail, Message: fmt.Sprintf("cannot resolve domain: %v", err),
			})
			return result
		}
	}

	// Select the first IPv4 address for port scanning, but check all IPs for CDN.
	targetIP := selectIPv4(ips)

	// CDN detection (check all IPs)
	cdn := detectCDNAny(ips)
	if cdn != "" {
		result.Checks = append(result.Checks, types.CheckResult{
			Status:  types.StatusOK,
			Message: fmt.Sprintf("CDN detected: %s", cdn),
		})
	} else {
		result.Checks = append(result.Checks, types.CheckResult{
			Status:  types.StatusOK,
			Message: "not behind a CDN",
		})
	}
	result.CDN = cdn

	// Common ports check
	result.Checks = append(result.Checks, checkPorts(targetIP)...)

	return result
}

func selectIPv4(ips []string) string {
	for _, ip := range ips {
		if parsed := net.ParseIP(ip); parsed != nil && parsed.To4() != nil {
			return ip
		}
	}
	return ips[0]
}

func detectCDNAny(ips []string) string {
	for _, ip := range ips {
		if cdn := detectCDN(ip); cdn != "" {
			return cdn
		}
	}
	return ""
}

func detectCDN(ip string) string {
	for provider, prefixes := range cdnRanges {
		for _, prefix := range prefixes {
			if strings.HasPrefix(ip, prefix) {
				return provider
			}
		}
	}
	return ""
}

func checkPorts(ip string) []types.CheckResult {
	var checks []types.CheckResult
	var mu sync.Mutex
	var wg sync.WaitGroup

	host := ip
	if strings.Contains(ip, ":") {
		host = "[" + ip + "]"
	}

	for port, name := range commonPorts {
		wg.Add(1)
		go func(port int, name string) {
			defer wg.Done()
			addr := net.JoinHostPort(host, fmt.Sprintf("%d", port))
			conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
			if err == nil {
				conn.Close()
				mu.Lock()
				checks = append(checks, types.CheckResult{
					Status:  types.StatusInfo,
					Message: fmt.Sprintf("port %d (%s) open", port, name),
				})
				mu.Unlock()
			}
		}(port, name)
	}
	wg.Wait()

	return checks
}

