package collector

import (
	"bee/audit/internal/schema"
	"encoding/json"
	"log/slog"
	"strconv"
	"strings"
)

type raidControllerTelemetry struct {
	BatteryChargePct       *float64
	BatteryHealthPct       *float64
	BatteryTemperatureC    *float64
	BatteryVoltageV        *float64
	BatteryReplaceRequired *bool
	ErrorDescription       *string
}

func enrichPCIeWithRAIDTelemetry(devs []schema.HardwarePCIeDevice) []schema.HardwarePCIeDevice {
	byVendor := collectRAIDControllerTelemetry()
	if len(byVendor) == 0 {
		return devs
	}

	positions := map[int]int{}
	for i := range devs {
		if devs[i].VendorID == nil || !isLikelyRAIDController(devs[i]) {
			continue
		}
		vendor := *devs[i].VendorID
		list := byVendor[vendor]
		if len(list) == 0 {
			continue
		}
		index := positions[vendor]
		if index >= len(list) {
			continue
		}
		positions[vendor] = index + 1
		applyRAIDControllerTelemetry(&devs[i], list[index])
	}

	return devs
}

func applyRAIDControllerTelemetry(dev *schema.HardwarePCIeDevice, tel raidControllerTelemetry) {
	if tel.BatteryChargePct != nil {
		dev.BatteryChargePct = tel.BatteryChargePct
	}
	if tel.BatteryHealthPct != nil {
		dev.BatteryHealthPct = tel.BatteryHealthPct
	}
	if tel.BatteryTemperatureC != nil {
		dev.BatteryTemperatureC = tel.BatteryTemperatureC
	}
	if tel.BatteryVoltageV != nil {
		dev.BatteryVoltageV = tel.BatteryVoltageV
	}
	if tel.BatteryReplaceRequired != nil {
		dev.BatteryReplaceRequired = tel.BatteryReplaceRequired
	}
	if tel.ErrorDescription != nil {
		dev.ErrorDescription = tel.ErrorDescription
		if dev.Status == nil || *dev.Status == statusOK {
			status := statusWarning
			dev.Status = &status
		}
	}
}

func collectRAIDControllerTelemetry() map[int][]raidControllerTelemetry {
	out := map[int][]raidControllerTelemetry{}

	if raw, err := raidToolQuery("storcli64", "/call", "show", "all", "J"); err == nil {
		list := parseStorcliControllerTelemetry(raw)
		if len(list) > 0 {
			out[vendorBroadcomLSI] = append(out[vendorBroadcomLSI], list...)
			slog.Info("raid: storcli controller telemetry", "count", len(list))
		}
	}

	if raw, err := raidToolQuery("ssacli", "ctrl", "all", "show", "config", "detail"); err == nil {
		list := parseSSACLIControllerTelemetry(string(raw))
		if len(list) > 0 {
			out[vendorHPE] = append(out[vendorHPE], list...)
			slog.Info("raid: ssacli controller telemetry", "count", len(list))
		}
	}

	if raw, err := raidToolQuery("arcconf", "getconfig", "1", "ad"); err == nil {
		list := parseArcconfControllerTelemetry(string(raw))
		if len(list) > 0 {
			out[vendorAdaptec] = append(out[vendorAdaptec], list...)
			slog.Info("raid: arcconf controller telemetry", "count", len(list))
		}
	}

	return out
}

func parseStorcliControllerTelemetry(raw []byte) []raidControllerTelemetry {
	var doc struct {
		Controllers []struct {
			ResponseData map[string]any `json:"Response Data"`
		} `json:"Controllers"`
	}
	if err := json.Unmarshal(raw, &doc); err != nil {
		slog.Warn("raid: parse storcli controller telemetry failed", "err", err)
		return nil
	}

	var out []raidControllerTelemetry
	for _, ctl := range doc.Controllers {
		tel := raidControllerTelemetry{}
		mergeStorcliBatteryMap(&tel, nestedStringMap(ctl.ResponseData["BBU_Info"]))
		mergeStorcliBatteryMap(&tel, nestedStringMap(ctl.ResponseData["BBU_Info_Details"]))
		mergeStorcliBatteryMap(&tel, nestedStringMap(ctl.ResponseData["CV_Info"]))
		mergeStorcliBatteryMap(&tel, nestedStringMap(ctl.ResponseData["CV_Info_Details"]))
		if hasRAIDControllerTelemetry(tel) {
			out = append(out, tel)
		}
	}
	return out
}

func nestedStringMap(raw any) map[string]string {
	switch value := raw.(type) {
	case map[string]any:
		out := map[string]string{}
		flattenStringMap("", value, out)
		return out
	case []any:
		out := map[string]string{}
		for _, item := range value {
			if m, ok := item.(map[string]any); ok {
				flattenStringMap("", m, out)
			}
		}
		return out
	default:
		return nil
	}
}

func flattenStringMap(prefix string, in map[string]any, out map[string]string) {
	for key, raw := range in {
		fullKey := strings.TrimSpace(strings.ToLower(strings.Trim(prefix+" "+key, " ")))
		switch value := raw.(type) {
		case map[string]any:
			flattenStringMap(fullKey, value, out)
		case []any:
			for _, item := range value {
				if m, ok := item.(map[string]any); ok {
					flattenStringMap(fullKey, m, out)
				}
			}
		case string:
			out[fullKey] = value
		case json.Number:
			out[fullKey] = value.String()
		case float64:
			out[fullKey] = strconv.FormatFloat(value, 'f', -1, 64)
		case bool:
			if value {
				out[fullKey] = "true"
			} else {
				out[fullKey] = "false"
			}
		}
	}
}

func mergeStorcliBatteryMap(tel *raidControllerTelemetry, fields map[string]string) {
	if len(fields) == 0 {
		return
	}
	for key, raw := range fields {
		lower := strings.ToLower(strings.TrimSpace(key))
		switch {
		case strings.Contains(lower, "relative state of charge"), strings.Contains(lower, "remaining capacity"), strings.Contains(lower, "charge"):
			if tel.BatteryChargePct == nil {
				tel.BatteryChargePct = parsePercentPtr(raw)
			}
		case strings.Contains(lower, "state of health"), strings.Contains(lower, "health"):
			if tel.BatteryHealthPct == nil {
				tel.BatteryHealthPct = parsePercentPtr(raw)
			}
		case strings.Contains(lower, "temperature"):
			if tel.BatteryTemperatureC == nil {
				tel.BatteryTemperatureC = parseFloatPtr(raw)
			}
		case strings.Contains(lower, "voltage"):
			if tel.BatteryVoltageV == nil {
				tel.BatteryVoltageV = parseFloatPtr(raw)
			}
		case strings.Contains(lower, "replace"), strings.Contains(lower, "replacement required"):
			if tel.BatteryReplaceRequired == nil {
				tel.BatteryReplaceRequired = parseReplaceRequired(raw)
			}
		case strings.Contains(lower, "learn cycle requested"), strings.Contains(lower, "battery state"), strings.Contains(lower, "capacitance state"):
			if desc := batteryStateDescription(raw); desc != nil && tel.ErrorDescription == nil {
				tel.ErrorDescription = desc
			}
		}
	}
}

func parseSSACLIControllerTelemetry(raw string) []raidControllerTelemetry {
	lines := strings.Split(raw, "\n")
	var out []raidControllerTelemetry
	var current *raidControllerTelemetry

	flush := func() {
		if current != nil && hasRAIDControllerTelemetry(*current) {
			out = append(out, *current)
		}
		current = nil
	}

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if trimmed == "" {
			continue
		}
		if strings.HasPrefix(strings.ToLower(trimmed), "smart array") || strings.HasPrefix(strings.ToLower(trimmed), "controller ") {
			flush()
			current = &raidControllerTelemetry{}
			continue
		}
		if current == nil {
			continue
		}
		if idx := strings.Index(trimmed, ":"); idx > 0 {
			key := strings.ToLower(strings.TrimSpace(trimmed[:idx]))
			val := strings.TrimSpace(trimmed[idx+1:])
			switch {
			case strings.Contains(key, "capacitor temperature"), strings.Contains(key, "battery temperature"):
				current.BatteryTemperatureC = parseFloatPtr(val)
			case strings.Contains(key, "capacitor voltage"), strings.Contains(key, "battery voltage"):
				current.BatteryVoltageV = parseFloatPtr(val)
			case strings.Contains(key, "capacitor charge"), strings.Contains(key, "battery charge"):
				current.BatteryChargePct = parsePercentPtr(val)
			case strings.Contains(key, "capacitor health"), strings.Contains(key, "battery health"):
				current.BatteryHealthPct = parsePercentPtr(val)
			case strings.Contains(key, "replace") || strings.Contains(key, "failed"):
				if current.BatteryReplaceRequired == nil {
					current.BatteryReplaceRequired = parseReplaceRequired(val)
				}
				if desc := batteryStateDescription(val); desc != nil && current.ErrorDescription == nil {
					current.ErrorDescription = desc
				}
			}
		}
	}
	flush()
	return out
}

func parseArcconfControllerTelemetry(raw string) []raidControllerTelemetry {
	lines := strings.Split(raw, "\n")
	tel := raidControllerTelemetry{}
	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if idx := strings.Index(trimmed, ":"); idx > 0 {
			key := strings.ToLower(strings.TrimSpace(trimmed[:idx]))
			val := strings.TrimSpace(trimmed[idx+1:])
			switch {
			case strings.Contains(key, "battery temperature"), strings.Contains(key, "capacitor temperature"):
				tel.BatteryTemperatureC = parseFloatPtr(val)
			case strings.Contains(key, "battery voltage"), strings.Contains(key, "capacitor voltage"):
				tel.BatteryVoltageV = parseFloatPtr(val)
			case strings.Contains(key, "battery charge"), strings.Contains(key, "capacitor charge"):
				tel.BatteryChargePct = parsePercentPtr(val)
			case strings.Contains(key, "battery health"), strings.Contains(key, "capacitor health"):
				tel.BatteryHealthPct = parsePercentPtr(val)
			case strings.Contains(key, "replace"), strings.Contains(key, "failed"):
				if tel.BatteryReplaceRequired == nil {
					tel.BatteryReplaceRequired = parseReplaceRequired(val)
				}
				if desc := batteryStateDescription(val); desc != nil && tel.ErrorDescription == nil {
					tel.ErrorDescription = desc
				}
			}
		}
	}
	if hasRAIDControllerTelemetry(tel) {
		return []raidControllerTelemetry{tel}
	}
	return nil
}

func hasRAIDControllerTelemetry(tel raidControllerTelemetry) bool {
	return tel.BatteryChargePct != nil ||
		tel.BatteryHealthPct != nil ||
		tel.BatteryTemperatureC != nil ||
		tel.BatteryVoltageV != nil ||
		tel.BatteryReplaceRequired != nil ||
		tel.ErrorDescription != nil
}

func parsePercentPtr(raw string) *float64 {
	raw = strings.ReplaceAll(strings.TrimSpace(raw), "%", "")
	return parseFloatPtr(raw)
}

func parseReplaceRequired(raw string) *bool {
	lower := strings.ToLower(strings.TrimSpace(raw))
	switch {
	case lower == "":
		return nil
	case strings.Contains(lower, "replace"), strings.Contains(lower, "failed"), strings.Contains(lower, "yes"), strings.Contains(lower, "required"):
		value := true
		return &value
	case strings.Contains(lower, "no"), strings.Contains(lower, "ok"), strings.Contains(lower, "good"), strings.Contains(lower, "optimal"):
		value := false
		return &value
	default:
		return nil
	}
}

func batteryStateDescription(raw string) *string {
	lower := strings.ToLower(strings.TrimSpace(raw))
	if lower == "" {
		return nil
	}
	switch {
	case strings.Contains(lower, "failed"), strings.Contains(lower, "fault"), strings.Contains(lower, "replace"), strings.Contains(lower, "warning"), strings.Contains(lower, "degraded"):
		return &raw
	default:
		return nil
	}
}
