package redfishprofile

import "strings"

func lenovoProfile() Profile {
	return staticProfile{
		name:            "lenovo",
		priority:        20,
		safeForFallback: true,
		matchFn: func(s MatchSignals) int {
			score := 0
			if containsFold(s.SystemManufacturer, "lenovo") ||
				containsFold(s.ChassisManufacturer, "lenovo") {
				score += 80
			}
			for _, ns := range s.OEMNamespaces {
				if containsFold(ns, "lenovo") {
					score += 30
					break
				}
			}
			// Lenovo XClarity Controller (XCC) is the BMC product line.
			if containsFold(s.ServiceRootProduct, "xclarity") ||
				containsFold(s.ServiceRootProduct, "xcc") {
				score += 30
			}
			return min(score, 100)
		},
		extendAcquisition: func(plan *AcquisitionPlan, _ MatchSignals) {
			// Lenovo XCC BMC exposes Chassis/1/Sensors with hundreds of individual
			// sensor member documents (e.g. Chassis/1/Sensors/101L1). These are
			// not used by any LOGPile parser — thermal/power data is read from
			// the aggregate Chassis/*/Thermal and Chassis/*/Power endpoints. On
			// a real server they largely return errors, wasting many minutes.
			// Lenovo OEM subtrees under Oem/Lenovo/LEDs and Oem/Lenovo/Slots also
			// enumerate dozens of individual documents not relevant to inventory.
			ensureSnapshotExcludeContains(plan,
				"/Sensors/",                             // individual sensor docs (Chassis/1/Sensors/NNN)
				"/Oem/Lenovo/LEDs/",                     // individual LED status entries (~47 per server)
				"/Oem/Lenovo/Slots/",                    // individual slot detail entries (~26 per server)
				"/Oem/Lenovo/Metrics/",                  // operational metrics, not inventory
				"/Oem/Lenovo/History",                   // historical telemetry
				"/Oem/Lenovo/Configuration",             // BMC config service, not inventory
				"/Oem/Lenovo/DateTimeService",           // BMC time service config
				"/Oem/Lenovo/GroupService",              // XCC fleet/group management state
				"/Oem/Lenovo/Recipients",                // alert recipient config
				"/Oem/Lenovo/RemoteControl",             // remote-media/session management
				"/Oem/Lenovo/RemoteMap",                 // remote-media mapping config
				"/Oem/Lenovo/SecureKeyLifecycleService", // key lifecycle/cert config
				"/Oem/Lenovo/ServerProfile",             // profile export/import config
				"/Oem/Lenovo/ServiceData",               // support/service metadata
				"/Oem/Lenovo/SsoCertificates",           // SSO certificate config
				"/Oem/Lenovo/SystemGuard",               // snapshot/history service
				"/Oem/Lenovo/Watchdogs",                 // watchdog config
				"/Oem/Lenovo/ScheduledPower",            // power scheduling config
				"/Oem/Lenovo/BootSettings/BootOrder",    // individual boot order lists
				"/NetworkProtocol/Oem/Lenovo/",          // DNS/LDAP/SMTP/SNMP manager config
				"/PortForwardingMap/",                   // network port forwarding config
				"/VirtualMedia/",                        // virtual media inventory/config, not hardware
				"/Boot/Certificates",                    // secure boot certificate stores, not inventory
				"/ThermalSubsystem/Fans/",               // per-fan member docs; replay uses aggregate Thermal only
			)
			// Lenovo XCC BMC is typically slow (p95 latency often 3-5s even under
			// normal load). Set rate thresholds that don't over-throttle on the
			// first few requests, and give the ETA estimator a realistic baseline.
			ensureRatePolicy(plan, AcquisitionRatePolicy{
				TargetP95LatencyMS:      2000,
				ThrottleP95LatencyMS:    4000,
				MinSnapshotWorkers:      2,
				MinPrefetchWorkers:      1,
				DisablePrefetchOnErrors: true,
			})
			ensureETABaseline(plan, AcquisitionETABaseline{
				DiscoverySeconds:     15,
				SnapshotSeconds:      120,
				PrefetchSeconds:      30,
				CriticalPlanBSeconds: 40,
				ProfilePlanBSeconds:  20,
			})
			addPlanNote(plan, "lenovo xcc acquisition extensions enabled: noisy sensor/oem paths excluded from snapshot")
		},
		refineAcquisition: func(resolved *ResolvedAcquisitionPlan, discovered DiscoveredResources, signals MatchSignals) {
			allowedChassis := lenovoAllowedInventoryChassis(discovered.ChassisPaths, signals.ResourceHints)
			resolved.SeedPaths = filterLenovoChassisInventoryPaths(resolved.SeedPaths, allowedChassis)
			resolved.CriticalPaths = filterLenovoChassisInventoryPaths(resolved.CriticalPaths, allowedChassis)
			resolved.Plan.SeedPaths = filterLenovoChassisInventoryPaths(resolved.Plan.SeedPaths, allowedChassis)
			resolved.Plan.CriticalPaths = filterLenovoChassisInventoryPaths(resolved.Plan.CriticalPaths, allowedChassis)
			resolved.Plan.PlanBPaths = filterLenovoChassisInventoryPaths(resolved.Plan.PlanBPaths, allowedChassis)
		},
	}
}

func lenovoAllowedInventoryChassis(chassisPaths, resourceHints []string) map[string]struct{} {
	allowed := make(map[string]struct{}, len(chassisPaths))
	for _, chassisPath := range chassisPaths {
		normalized := normalizePath(chassisPath)
		if normalized == "" {
			continue
		}
		if normalized == "/redfish/v1/Chassis/1" {
			allowed[normalized] = struct{}{}
			continue
		}
		for _, hint := range resourceHints {
			hint = normalizePath(hint)
			if !strings.HasPrefix(hint, normalized+"/") {
				continue
			}
			if lenovoHintLooksLikeChassisInventory(hint) {
				allowed[normalized] = struct{}{}
				break
			}
		}
	}
	return allowed
}

func lenovoHintLooksLikeChassisInventory(path string) bool {
	for _, suffix := range []string{
		"/Power",
		"/PowerSubsystem",
		"/PowerSubsystem/PowerSupplies",
		"/Thermal",
		"/ThresholdSensors",
		"/DiscreteSensors",
		"/SensorsList",
		"/NetworkAdapters",
		"/PCIeDevices",
		"/Drives",
		"/Assembly",
	} {
		if strings.HasSuffix(path, suffix) || strings.Contains(path, suffix+"/") {
			return true
		}
	}
	return false
}

func filterLenovoChassisInventoryPaths(paths []string, allowedChassis map[string]struct{}) []string {
	if len(paths) == 0 {
		return nil
	}
	out := make([]string, 0, len(paths))
	for _, path := range paths {
		normalized := normalizePath(path)
		chassis := lenovoPathChassisRoot(normalized)
		if chassis == "" {
			out = append(out, normalized)
			continue
		}
		if normalized == chassis {
			out = append(out, normalized)
			continue
		}
		if _, ok := allowedChassis[chassis]; ok {
			out = append(out, normalized)
		}
	}
	return dedupeSorted(out)
}

func lenovoPathChassisRoot(path string) string {
	const prefix = "/redfish/v1/Chassis/"
	if !strings.HasPrefix(path, prefix) {
		return ""
	}
	rest := strings.TrimPrefix(path, prefix)
	if rest == "" {
		return ""
	}
	if idx := strings.IndexByte(rest, '/'); idx >= 0 {
		return prefix + rest[:idx]
	}
	return prefix + rest
}
