package iace

import (
	"sort"

	"github.com/google/uuid"
)

// HazardBlock groups related hazards under a parent hazard.
// The parent is the hazard with the highest inherent risk in the group.
// Child hazards are covered by the same or similar protective measures.
type HazardBlock struct {
	ParentHazard HazardBlockEntry   `json:"parent_hazard"`
	Children     []HazardBlockEntry `json:"children"`
	BlockKey     string             `json:"block_key"`
	SharedMeasureCount int          `json:"shared_measure_count"`
	// If true, the parent's measures cover all children → children
	// don't need individual risk assessment.
	ChildrenCoveredByParent bool `json:"children_covered_by_parent"`
}

// HazardBlockEntry is a hazard with its assessment and linked measures.
type HazardBlockEntry struct {
	Hazard         Hazard          `json:"hazard"`
	Assessment     *RiskAssessment `json:"assessment,omitempty"`
	MitigationIDs  []uuid.UUID     `json:"mitigation_ids"`
}

// ComputeHazardBlocks groups hazards into blocks based on category + component.
// Within each block, the hazard with the highest risk becomes the parent.
// Children whose measures are a subset of the parent's measures are marked as covered.
func ComputeHazardBlocks(
	hazards []Hazard,
	assessments []RiskAssessment,
	mitigations []Mitigation,
) []HazardBlock {
	if len(hazards) == 0 {
		return nil
	}

	// Build assessment lookup: hazard_id → latest assessment
	assessMap := make(map[uuid.UUID]*RiskAssessment)
	for i := range assessments {
		a := &assessments[i]
		if existing, ok := assessMap[a.HazardID]; !ok || a.Version > existing.Version {
			assessMap[a.HazardID] = a
		}
	}

	// Build mitigation lookup: hazard_id → []mitigation_ids
	mitsByHazard := make(map[uuid.UUID][]uuid.UUID)
	for _, m := range mitigations {
		mitsByHazard[m.HazardID] = append(mitsByHazard[m.HazardID], m.ID)
	}

	// Group by blockKey = category + ":" + componentID
	groups := make(map[string][]HazardBlockEntry)
	for _, h := range hazards {
		key := buildBlockKey(h)
		entry := HazardBlockEntry{
			Hazard:        h,
			Assessment:    assessMap[h.ID],
			MitigationIDs: mitsByHazard[h.ID],
		}
		groups[key] = append(groups[key], entry)
	}

	// Build blocks: sort each group by risk, first is parent
	var blocks []HazardBlock
	for key, entries := range groups {
		sortByRiskDesc(entries, assessMap)

		parent := entries[0]
		children := entries[1:]

		// Check if parent's measures cover children
		parentMitSet := toUUIDSet(parent.MitigationIDs)
		allCovered := true
		for _, child := range children {
			if !mitigationsCoveredBy(child, parent, mitigations) {
				allCovered = false
				break
			}
		}

		block := HazardBlock{
			ParentHazard:            parent,
			Children:                children,
			BlockKey:                key,
			SharedMeasureCount:      len(parentMitSet),
			ChildrenCoveredByParent: allCovered && len(children) > 0,
		}
		blocks = append(blocks, block)
	}

	// Sort blocks: largest (most children) first, then by parent risk
	sort.Slice(blocks, func(i, j int) bool {
		ri := inherentRisk(blocks[i].ParentHazard, assessMap)
		rj := inherentRisk(blocks[j].ParentHazard, assessMap)
		if len(blocks[i].Children) != len(blocks[j].Children) {
			return len(blocks[i].Children) > len(blocks[j].Children)
		}
		return ri > rj
	})

	return blocks
}

func buildBlockKey(h Hazard) string {
	// Group by category + component. Hazards at the same component in the
	// same category form one block — the zone is typically different but the
	// protective measures (e.g. Schutzzaun, Sicherheitszuhaltung) are shared.
	return h.Category + ":" + h.ComponentID.String()
}

func sortByRiskDesc(entries []HazardBlockEntry, assessMap map[uuid.UUID]*RiskAssessment) {
	sort.Slice(entries, func(i, j int) bool {
		ri := inherentRisk(entries[i], assessMap)
		rj := inherentRisk(entries[j], assessMap)
		return ri > rj
	})
}

func inherentRisk(entry HazardBlockEntry, assessMap map[uuid.UUID]*RiskAssessment) float64 {
	if entry.Assessment != nil {
		return entry.Assessment.InherentRisk
	}
	if a, ok := assessMap[entry.Hazard.ID]; ok {
		return a.InherentRisk
	}
	return 0
}

// mitigationsCoveredBy checks if child's measures are functionally covered
// by parent's measures (same reduction type and hazard category).
func mitigationsCoveredBy(child, parent HazardBlockEntry, allMits []Mitigation) bool {
	if len(child.MitigationIDs) == 0 {
		return true // No measures needed → covered by default
	}

	mitMap := make(map[uuid.UUID]Mitigation)
	for _, m := range allMits {
		mitMap[m.ID] = m
	}

	// Check: for each child mitigation type, parent has same type
	parentTypes := make(map[ReductionType]bool)
	for _, mid := range parent.MitigationIDs {
		if m, ok := mitMap[mid]; ok {
			parentTypes[m.ReductionType] = true
		}
	}

	for _, mid := range child.MitigationIDs {
		if m, ok := mitMap[mid]; ok {
			if !parentTypes[m.ReductionType] {
				return false
			}
		}
	}
	return true
}

func toUUIDSet(ids []uuid.UUID) map[uuid.UUID]bool {
	s := make(map[uuid.UUID]bool, len(ids))
	for _, id := range ids {
		s[id] = true
	}
	return s
}
