package discovery

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

	"github.com/rcourtman/pulse-go-rewrite/internal/config"
	pkgdiscovery "github.com/rcourtman/pulse-go-rewrite/pkg/discovery"
	"github.com/rcourtman/pulse-go-rewrite/pkg/discovery/envdetect"
)

// BuildScanner creates a discovery scanner configured using the supplied discovery config.
func BuildScanner(cfg config.DiscoveryConfig) (*pkgdiscovery.Scanner, error) {
	cfg = config.NormalizeDiscoveryConfig(cfg)

	profile, err := envdetect.DetectEnvironment()
	if err != nil {
		return nil, err
	}

	ApplyConfigToProfile(profile, cfg)
	return pkgdiscovery.NewScannerWithProfile(profile), nil
}

// ApplyConfigToProfile mutates the supplied environment profile according to the discovery config.
func ApplyConfigToProfile(profile *envdetect.EnvironmentProfile, cfg config.DiscoveryConfig) {
	if profile == nil {
		return
	}

	// Environment override
	if env, ok := environmentFromOverride(cfg.EnvironmentOverride); ok {
		profile.Type = env
		filterPhasesForEnvironment(profile, env)
	} else if cfg.EnvironmentOverride != "" && strings.ToLower(cfg.EnvironmentOverride) != "auto" {
		profile.Warnings = append(profile.Warnings, fmt.Sprintf("Unknown environment override: %s", cfg.EnvironmentOverride))
	}

	// Apply subnet blocklist
	blocked := parseCIDRMap(cfg.SubnetBlocklist, &profile.Warnings)
	if len(blocked) > 0 {
		var filtered []envdetect.SubnetPhase
		for _, phase := range profile.Phases {
			var kept []net.IPNet
			for _, subnet := range phase.Subnets {
				if _, blocked := blocked[subnet.String()]; blocked {
					continue
				}
				kept = append(kept, subnet)
			}
			if len(kept) > 0 {
				phase.Subnets = kept
				filtered = append(filtered, phase)
			}
		}
		profile.Phases = filtered
	}

	// Apply subnet allowlist as highest priority phase
	if len(cfg.SubnetAllowlist) > 0 {
		allowlist := parseCIDRs(cfg.SubnetAllowlist, &profile.Warnings)
		if len(allowlist) > 0 {
			if len(blocked) > 0 {
				filtered := allowlist[:0]
				for _, subnet := range allowlist {
					if _, skip := blocked[subnet.String()]; skip {
						continue
					}
					filtered = append(filtered, subnet)
				}
				allowlist = filtered
			}
		}
		if len(allowlist) > 0 {
			allowPhase := envdetect.SubnetPhase{
				Name:       "config_allowlist",
				Subnets:    allowlist,
				Confidence: 1.0,
				Priority:   0,
			}
			profile.Phases = append([]envdetect.SubnetPhase{allowPhase}, profile.Phases...)
		}
	}

	if len(cfg.SubnetAllowlist) == 0 && shouldPruneContainerNetworks(profile.Type) {
		pruned := make([]envdetect.SubnetPhase, 0, len(profile.Phases))
		for _, phase := range profile.Phases {
			if isLikelyContainerPhase(phase.Name) {
				continue
			}
			pruned = append(pruned, phase)
		}
		if len(pruned) > 0 {
			profile.Phases = pruned
		}
	}

	// Override scan policy
	if cfg.MaxHostsPerScan > 0 {
		profile.Policy.MaxHostsPerScan = cfg.MaxHostsPerScan
	}
	if cfg.MaxConcurrent > 0 {
		profile.Policy.MaxConcurrent = cfg.MaxConcurrent
	}
	profile.Policy.EnableReverseDNS = cfg.EnableReverseDNS
	profile.Policy.ScanGateways = cfg.ScanGateways

	if cfg.DialTimeout > 0 {
		profile.Policy.DialTimeout = time.Duration(cfg.DialTimeout) * time.Millisecond
	}
	if cfg.HTTPTimeout > 0 {
		profile.Policy.HTTPTimeout = time.Duration(cfg.HTTPTimeout) * time.Millisecond
	}
}

func shouldPruneContainerNetworks(env envdetect.Environment) bool {
	return env == envdetect.DockerBridge || env == envdetect.LXCUnprivileged
}

func isLikelyContainerPhase(name string) bool {
	name = strings.ToLower(strings.TrimSpace(name))
	return strings.Contains(name, "container")
}

func parseCIDRs(values []string, warnings *[]string) []net.IPNet {
	var subnets []net.IPNet
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		_, ipNet, err := net.ParseCIDR(value)
		if err != nil {
			if warnings != nil {
				*warnings = append(*warnings, fmt.Sprintf("Invalid CIDR '%s' ignored", value))
			}
			continue
		}
		subnets = append(subnets, *ipNet)
	}
	return subnets
}

func parseCIDRMap(values []string, warnings *[]string) map[string]struct{} {
	cidrs := parseCIDRs(values, warnings)
	result := make(map[string]struct{}, len(cidrs))
	for _, cidr := range cidrs {
		result[cidr.String()] = struct{}{}
	}
	return result
}

func environmentFromOverride(value string) (envdetect.Environment, bool) {
	normalized := strings.ToLower(strings.TrimSpace(value))
	switch normalized {
	case "", "auto":
		return envdetect.Unknown, false
	case "native":
		return envdetect.Native, true
	case "docker_host":
		return envdetect.DockerHost, true
	case "docker_bridge":
		return envdetect.DockerBridge, true
	case "lxc_privileged":
		return envdetect.LXCPrivileged, true
	case "lxc_unprivileged":
		return envdetect.LXCUnprivileged, true
	default:
		return envdetect.Unknown, false
	}
}

func filterPhasesForEnvironment(profile *envdetect.EnvironmentProfile, env envdetect.Environment) {
	if len(profile.Phases) == 0 {
		return
	}

	var keep []envdetect.SubnetPhase
	for _, phase := range profile.Phases {
		name := strings.ToLower(phase.Name)
		switch env {
		case envdetect.Native, envdetect.DockerHost, envdetect.LXCPrivileged:
			if strings.Contains(name, "local") || strings.Contains(name, "host") {
				keep = append(keep, phase)
			}
		case envdetect.DockerBridge:
			if strings.Contains(name, "container") || strings.Contains(name, "inferred") || strings.Contains(name, "host") {
				keep = append(keep, phase)
			}
		case envdetect.LXCUnprivileged:
			if strings.Contains(name, "lxc") || strings.Contains(name, "container") || strings.Contains(name, "parent") {
				keep = append(keep, phase)
			}
		default:
			keep = append(keep, phase)
		}
	}

	if len(keep) > 0 {
		profile.Phases = keep
	}
}
