package easy_bee

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"git.mchus.pro/mchus/logpile/internal/models"
	"git.mchus.pro/mchus/logpile/internal/parser"
)

const parserVersion = "1.0"

func init() {
	parser.Register(&Parser{})
}

// Parser imports support bundles produced by reanimator-easy-bee.
// These archives embed a ready-to-use hardware snapshot in export/bee-audit.json.
type Parser struct{}

func (p *Parser) Name() string {
	return "Reanimator Easy Bee Parser"
}

func (p *Parser) Vendor() string {
	return "easy_bee"
}

func (p *Parser) Version() string {
	return parserVersion
}

func (p *Parser) Detect(files []parser.ExtractedFile) int {
	confidence := 0
	hasManifest := false
	hasBeeAudit := false
	hasRuntimeHealth := false
	hasTechdump := false
	hasBundlePrefix := false

	for _, f := range files {
		path := strings.ToLower(strings.TrimSpace(f.Path))
		content := strings.ToLower(string(f.Content))

		if !hasBundlePrefix && strings.Contains(path, "bee-support-") {
			hasBundlePrefix = true
			confidence += 5
		}

		if (strings.HasSuffix(path, "/manifest.txt") || path == "manifest.txt") &&
			strings.Contains(content, "bee_version=") {
			hasManifest = true
			confidence += 35
			if strings.Contains(content, "export_dir=") {
				confidence += 10
			}
		}

		if strings.HasSuffix(path, "/export/bee-audit.json") || path == "bee-audit.json" {
			hasBeeAudit = true
			confidence += 55
		}

		if hasBundlePrefix && (strings.HasSuffix(path, "/export/runtime-health.json") || path == "runtime-health.json") {
			hasRuntimeHealth = true
			confidence += 10
		}

		if hasBundlePrefix && !hasTechdump && strings.Contains(path, "/export/techdump/") {
			hasTechdump = true
			confidence += 10
		}
	}

	if hasManifest && hasBeeAudit {
		return 100
	}
	if hasBeeAudit && (hasRuntimeHealth || hasTechdump) {
		confidence += 10
	}
	if confidence > 100 {
		return 100
	}
	return confidence
}

func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, error) {
	snapshotFile := findSnapshotFile(files)
	if snapshotFile == nil {
		return nil, fmt.Errorf("easy-bee snapshot not found")
	}

	var snapshot beeSnapshot
	if err := json.Unmarshal(snapshotFile.Content, &snapshot); err != nil {
		return nil, fmt.Errorf("decode %s: %w", snapshotFile.Path, err)
	}

	manifest := parseManifest(files)

	result := &models.AnalysisResult{
		SourceType:              strings.TrimSpace(snapshot.SourceType),
		Protocol:                strings.TrimSpace(snapshot.Protocol),
		TargetHost:              firstNonEmpty(snapshot.TargetHost, manifest.Host),
		SourceTimezone:          strings.TrimSpace(snapshot.SourceTimezone),
		CollectedAt:             chooseCollectedAt(snapshot, manifest),
		InventoryLastModifiedAt: snapshot.InventoryLastModifiedAt,
		RawPayloads:             snapshot.RawPayloads,
		Events:                  make([]models.Event, 0),
		FRU:                     append([]models.FRUInfo(nil), snapshot.FRU...),
		Sensors:                 make([]models.SensorReading, 0),
		Hardware: &models.HardwareConfig{
			Firmware:        append([]models.FirmwareInfo(nil), snapshot.Hardware.Firmware...),
			BoardInfo:       snapshot.Hardware.Board,
			Devices:         append([]models.HardwareDevice(nil), snapshot.Hardware.Devices...),
			CPUs:            append([]models.CPU(nil), snapshot.Hardware.CPUs...),
			Memory:          append([]models.MemoryDIMM(nil), snapshot.Hardware.Memory...),
			Storage:         append([]models.Storage(nil), snapshot.Hardware.Storage...),
			Volumes:         append([]models.StorageVolume(nil), snapshot.Hardware.Volumes...),
			PCIeDevices:     normalizePCIeDevices(snapshot.Hardware.PCIeDevices),
			GPUs:            append([]models.GPU(nil), snapshot.Hardware.GPUs...),
			NetworkCards:    append([]models.NIC(nil), snapshot.Hardware.NetworkCards...),
			NetworkAdapters: normalizeNetworkAdapters(snapshot.Hardware.NetworkAdapters),
			PowerSupply:     append([]models.PSU(nil), snapshot.Hardware.PowerSupply...),
		},
	}

	result.Events = append(result.Events, snapshot.Events...)
	result.Events = append(result.Events, convertRuntimeToEvents(snapshot.Runtime, result.CollectedAt)...)
	result.Events = append(result.Events, convertEventLogs(snapshot.Hardware.EventLogs)...)

	result.Sensors = append(result.Sensors, snapshot.Sensors...)
	result.Sensors = append(result.Sensors, flattenSensorGroups(snapshot.Hardware.Sensors)...)

	if len(result.FRU) == 0 {
		if boardFRU, ok := buildBoardFRU(snapshot.Hardware.Board); ok {
			result.FRU = append(result.FRU, boardFRU)
		}
	}

	if result.Hardware == nil || (result.Hardware.BoardInfo.SerialNumber == "" &&
		len(result.Hardware.CPUs) == 0 &&
		len(result.Hardware.Memory) == 0 &&
		len(result.Hardware.Storage) == 0 &&
		len(result.Hardware.PCIeDevices) == 0 &&
		len(result.Hardware.Devices) == 0) {
		return nil, fmt.Errorf("unsupported easy-bee snapshot format")
	}

	return result, nil
}

type beeSnapshot struct {
	SourceType              string                 `json:"source_type,omitempty"`
	Protocol                string                 `json:"protocol,omitempty"`
	TargetHost              string                 `json:"target_host,omitempty"`
	SourceTimezone          string                 `json:"source_timezone,omitempty"`
	CollectedAt             time.Time              `json:"collected_at,omitempty"`
	InventoryLastModifiedAt time.Time              `json:"inventory_last_modified_at,omitempty"`
	RawPayloads             map[string]any         `json:"raw_payloads,omitempty"`
	Events                  []models.Event         `json:"events,omitempty"`
	FRU                     []models.FRUInfo       `json:"fru,omitempty"`
	Sensors                 []models.SensorReading `json:"sensors,omitempty"`
	Hardware                beeHardware            `json:"hardware"`
	Runtime                 beeRuntime             `json:"runtime,omitempty"`
}

type beeHardware struct {
	Board           models.BoardInfo        `json:"board"`
	Firmware        []models.FirmwareInfo   `json:"firmware,omitempty"`
	Devices         []models.HardwareDevice `json:"devices,omitempty"`
	CPUs            []models.CPU            `json:"cpus,omitempty"`
	Memory          []models.MemoryDIMM     `json:"memory,omitempty"`
	Storage         []models.Storage        `json:"storage,omitempty"`
	Volumes         []models.StorageVolume  `json:"volumes,omitempty"`
	PCIeDevices     []models.PCIeDevice     `json:"pcie_devices,omitempty"`
	GPUs            []models.GPU            `json:"gpus,omitempty"`
	NetworkCards    []models.NIC            `json:"network_cards,omitempty"`
	NetworkAdapters []models.NetworkAdapter `json:"network_adapters,omitempty"`
	PowerSupply     []models.PSU            `json:"power_supplies,omitempty"`
	Sensors         beeSensorGroups         `json:"sensors,omitempty"`
	EventLogs       []beeEventLog           `json:"event_logs,omitempty"`
}

type beeSensorGroups struct {
	Fans         []beeFanSensor         `json:"fans,omitempty"`
	Power        []beePowerSensor       `json:"power,omitempty"`
	Temperatures []beeTemperatureSensor `json:"temperatures,omitempty"`
	Other        []beeOtherSensor       `json:"other,omitempty"`
}

type beeFanSensor struct {
	Name     string `json:"name"`
	Location string `json:"location,omitempty"`
	RPM      int    `json:"rpm,omitempty"`
	Status   string `json:"status,omitempty"`
}

type beePowerSensor struct {
	Name     string  `json:"name"`
	Location string  `json:"location,omitempty"`
	VoltageV float64 `json:"voltage_v,omitempty"`
	CurrentA float64 `json:"current_a,omitempty"`
	PowerW   float64 `json:"power_w,omitempty"`
	Status   string  `json:"status,omitempty"`
}

type beeTemperatureSensor struct {
	Name                     string  `json:"name"`
	Location                 string  `json:"location,omitempty"`
	Celsius                  float64 `json:"celsius,omitempty"`
	ThresholdWarningCelsius  float64 `json:"threshold_warning_celsius,omitempty"`
	ThresholdCriticalCelsius float64 `json:"threshold_critical_celsius,omitempty"`
	Status                   string  `json:"status,omitempty"`
}

type beeOtherSensor struct {
	Name     string  `json:"name"`
	Location string  `json:"location,omitempty"`
	Value    float64 `json:"value,omitempty"`
	Unit     string  `json:"unit,omitempty"`
	Status   string  `json:"status,omitempty"`
}

type beeRuntime struct {
	Status        string             `json:"status,omitempty"`
	CheckedAt     time.Time          `json:"checked_at,omitempty"`
	NetworkStatus string             `json:"network_status,omitempty"`
	Issues        []beeRuntimeIssue  `json:"issues,omitempty"`
	Services      []beeRuntimeStatus `json:"services,omitempty"`
	Interfaces    []beeInterface     `json:"interfaces,omitempty"`
}

type beeRuntimeIssue struct {
	Code        string `json:"code,omitempty"`
	Severity    string `json:"severity,omitempty"`
	Description string `json:"description,omitempty"`
}

type beeRuntimeStatus struct {
	Name   string `json:"name,omitempty"`
	Status string `json:"status,omitempty"`
}

type beeInterface struct {
	Name    string   `json:"name,omitempty"`
	State   string   `json:"state,omitempty"`
	IPv4    []string `json:"ipv4,omitempty"`
	Outcome string   `json:"outcome,omitempty"`
}

type beeEventLog struct {
	Source     string         `json:"source,omitempty"`
	EventTime  string         `json:"event_time,omitempty"`
	Severity   string         `json:"severity,omitempty"`
	MessageID  string         `json:"message_id,omitempty"`
	Message    string         `json:"message,omitempty"`
	RawPayload map[string]any `json:"raw_payload,omitempty"`
}

type manifestMetadata struct {
	Host           string
	GeneratedAtUTC time.Time
}

func findSnapshotFile(files []parser.ExtractedFile) *parser.ExtractedFile {
	for i := range files {
		path := strings.ToLower(strings.TrimSpace(files[i].Path))
		if strings.HasSuffix(path, "/export/bee-audit.json") || path == "bee-audit.json" {
			return &files[i]
		}
	}
	for i := range files {
		path := strings.ToLower(strings.TrimSpace(files[i].Path))
		if strings.HasSuffix(path, ".json") && strings.Contains(path, "reanimator") {
			return &files[i]
		}
	}
	return nil
}

func parseManifest(files []parser.ExtractedFile) manifestMetadata {
	var meta manifestMetadata

	for _, f := range files {
		path := strings.ToLower(strings.TrimSpace(f.Path))
		if !(strings.HasSuffix(path, "/manifest.txt") || path == "manifest.txt") {
			continue
		}

		lines := strings.Split(string(f.Content), "\n")
		for _, line := range lines {
			key, value, ok := strings.Cut(strings.TrimSpace(line), "=")
			if !ok {
				continue
			}
			switch strings.TrimSpace(key) {
			case "host":
				meta.Host = strings.TrimSpace(value)
			case "generated_at_utc":
				if ts, err := time.Parse(time.RFC3339, strings.TrimSpace(value)); err == nil {
					meta.GeneratedAtUTC = ts.UTC()
				}
			}
		}
		break
	}

	return meta
}

func chooseCollectedAt(snapshot beeSnapshot, manifest manifestMetadata) time.Time {
	switch {
	case !snapshot.CollectedAt.IsZero():
		return snapshot.CollectedAt.UTC()
	case !snapshot.Runtime.CheckedAt.IsZero():
		return snapshot.Runtime.CheckedAt.UTC()
	case !manifest.GeneratedAtUTC.IsZero():
		return manifest.GeneratedAtUTC.UTC()
	default:
		return time.Time{}
	}
}

func convertRuntimeToEvents(runtime beeRuntime, fallback time.Time) []models.Event {
	events := make([]models.Event, 0)
	ts := runtime.CheckedAt
	if ts.IsZero() {
		ts = fallback
	}

	if status := strings.TrimSpace(runtime.Status); status != "" {
		desc := "Bee runtime status: " + status
		if networkStatus := strings.TrimSpace(runtime.NetworkStatus); networkStatus != "" {
			desc += " (network: " + networkStatus + ")"
		}
		events = append(events, models.Event{
			Timestamp:   ts,
			Source:      "Bee Runtime",
			EventType:   "Runtime Status",
			Severity:    mapSeverity(status),
			Description: desc,
		})
	}

	for _, issue := range runtime.Issues {
		desc := strings.TrimSpace(issue.Description)
		if desc == "" {
			desc = "Bee runtime issue"
		}
		events = append(events, models.Event{
			Timestamp:   ts,
			Source:      "Bee Runtime",
			EventType:   "Runtime Issue",
			Severity:    mapSeverity(issue.Severity),
			Description: desc,
			RawData:     strings.TrimSpace(issue.Code),
		})
	}

	for _, svc := range runtime.Services {
		status := strings.TrimSpace(svc.Status)
		if status == "" || strings.EqualFold(status, "active") {
			continue
		}
		events = append(events, models.Event{
			Timestamp:   ts,
			Source:      "systemd",
			EventType:   "Service Status",
			Severity:    mapSeverity(status),
			Description: fmt.Sprintf("%s is %s", strings.TrimSpace(svc.Name), status),
		})
	}

	for _, iface := range runtime.Interfaces {
		state := strings.TrimSpace(iface.State)
		outcome := strings.TrimSpace(iface.Outcome)
		if state == "" && outcome == "" {
			continue
		}
		if strings.EqualFold(state, "up") && strings.EqualFold(outcome, "lease_acquired") {
			continue
		}
		desc := fmt.Sprintf("interface %s state=%s outcome=%s", strings.TrimSpace(iface.Name), state, outcome)
		events = append(events, models.Event{
			Timestamp:   ts,
			Source:      "network",
			EventType:   "Interface Status",
			Severity:    models.SeverityWarning,
			Description: strings.TrimSpace(desc),
		})
	}

	return events
}

func convertEventLogs(items []beeEventLog) []models.Event {
	events := make([]models.Event, 0, len(items))
	for _, item := range items {
		message := strings.TrimSpace(item.Message)
		if message == "" {
			continue
		}
		ts := parseEventTime(item.EventTime)
		rawData := strings.TrimSpace(item.MessageID)
		events = append(events, models.Event{
			Timestamp:   ts,
			Source:      firstNonEmpty(strings.TrimSpace(item.Source), "Reanimator"),
			EventType:   "Event Log",
			Severity:    mapSeverity(item.Severity),
			Description: message,
			RawData:     rawData,
		})
	}
	return events
}

func parseEventTime(raw string) time.Time {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return time.Time{}
	}
	layouts := []string{time.RFC3339Nano, time.RFC3339}
	for _, layout := range layouts {
		if ts, err := time.Parse(layout, raw); err == nil {
			return ts.UTC()
		}
	}
	return time.Time{}
}

func flattenSensorGroups(groups beeSensorGroups) []models.SensorReading {
	result := make([]models.SensorReading, 0, len(groups.Fans)+len(groups.Power)+len(groups.Temperatures)+len(groups.Other))

	for _, fan := range groups.Fans {
		result = append(result, models.SensorReading{
			Name:   sensorName(fan.Name, fan.Location),
			Type:   "fan",
			Value:  float64(fan.RPM),
			Unit:   "RPM",
			Status: strings.TrimSpace(fan.Status),
		})
	}

	for _, power := range groups.Power {
		name := sensorName(power.Name, power.Location)
		status := strings.TrimSpace(power.Status)
		if power.PowerW != 0 {
			result = append(result, models.SensorReading{
				Name:   name,
				Type:   "power",
				Value:  power.PowerW,
				Unit:   "W",
				Status: status,
			})
		}
		if power.VoltageV != 0 {
			result = append(result, models.SensorReading{
				Name:   name + " Voltage",
				Type:   "voltage",
				Value:  power.VoltageV,
				Unit:   "V",
				Status: status,
			})
		}
		if power.CurrentA != 0 {
			result = append(result, models.SensorReading{
				Name:   name + " Current",
				Type:   "current",
				Value:  power.CurrentA,
				Unit:   "A",
				Status: status,
			})
		}
	}

	for _, temp := range groups.Temperatures {
		result = append(result, models.SensorReading{
			Name:   sensorName(temp.Name, temp.Location),
			Type:   "temperature",
			Value:  temp.Celsius,
			Unit:   "C",
			Status: strings.TrimSpace(temp.Status),
		})
	}

	for _, other := range groups.Other {
		result = append(result, models.SensorReading{
			Name:   sensorName(other.Name, other.Location),
			Type:   "other",
			Value:  other.Value,
			Unit:   strings.TrimSpace(other.Unit),
			Status: strings.TrimSpace(other.Status),
		})
	}

	return result
}

func sensorName(name, location string) string {
	name = strings.TrimSpace(name)
	location = strings.TrimSpace(location)
	if name == "" {
		return location
	}
	if location == "" {
		return name
	}
	return name + " [" + location + "]"
}

func normalizePCIeDevices(items []models.PCIeDevice) []models.PCIeDevice {
	out := append([]models.PCIeDevice(nil), items...)
	for i := range out {
		slot := strings.TrimSpace(out[i].Slot)
		if out[i].BDF == "" && looksLikeBDF(slot) {
			out[i].BDF = slot
		}
		if out[i].Slot == "" && out[i].BDF != "" {
			out[i].Slot = out[i].BDF
		}
	}
	return out
}

func normalizeNetworkAdapters(items []models.NetworkAdapter) []models.NetworkAdapter {
	out := append([]models.NetworkAdapter(nil), items...)
	for i := range out {
		slot := strings.TrimSpace(out[i].Slot)
		if out[i].BDF == "" && looksLikeBDF(slot) {
			out[i].BDF = slot
		}
		if out[i].Slot == "" && out[i].BDF != "" {
			out[i].Slot = out[i].BDF
		}
	}
	return out
}

func looksLikeBDF(value string) bool {
	value = strings.TrimSpace(value)
	if len(value) != len("0000:00:00.0") {
		return false
	}
	for i, r := range value {
		switch i {
		case 4, 7:
			if r != ':' {
				return false
			}
		case 10:
			if r != '.' {
				return false
			}
		default:
			if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
				return false
			}
		}
	}
	return true
}

func buildBoardFRU(board models.BoardInfo) (models.FRUInfo, bool) {
	if strings.TrimSpace(board.SerialNumber) == "" &&
		strings.TrimSpace(board.Manufacturer) == "" &&
		strings.TrimSpace(board.ProductName) == "" &&
		strings.TrimSpace(board.PartNumber) == "" {
		return models.FRUInfo{}, false
	}

	return models.FRUInfo{
		Description:  "System Board",
		Manufacturer: strings.TrimSpace(board.Manufacturer),
		ProductName:  strings.TrimSpace(board.ProductName),
		SerialNumber: strings.TrimSpace(board.SerialNumber),
		PartNumber:   strings.TrimSpace(board.PartNumber),
	}, true
}

func mapSeverity(raw string) models.Severity {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "critical", "crit", "error", "failed", "failure":
		return models.SeverityCritical
	case "warning", "warn", "partial", "degraded", "inactive", "activating", "deactivating":
		return models.SeverityWarning
	default:
		return models.SeverityInfo
	}
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value != "" {
			return value
		}
	}
	return ""
}
