package audit

import (
	"sort"

	"github.com/breakpilot/ai-compliance-sdk/internal/iace"
)

// runConsistencyImpl asks: does this component, with its own tags PLUS the
// tags of its TypicalEnergySources, actually trigger at least one pattern
// in every category listed in its TypicalHazardCategories?
//
// A component declares "this is what I am dangerous for" and the engine
// turns that declaration into hazards through patterns. If no pattern can
// fire from the component's tag set, the declaration is decorative — the
// engine will never produce a hazard in that category for this component,
// even though the library author said it should.
func init() {
	runConsistencyImpl = runConsistency
}

func runConsistency() ConsistencyReport {
	comps := iace.GetComponentLibrary()
	energies := iace.GetEnergySources()
	patterns := iace.AllPatterns()

	energyByID := map[string]iace.EnergySourceEntry{}
	for _, e := range energies {
		energyByID[e.ID] = e
	}

	report := ConsistencyReport{TotalComponents: len(comps)}

	for _, c := range comps {
		if len(c.TypicalHazardCategories) == 0 {
			report.Consistent++
			continue
		}
		effective := buildEffectiveTags(c, energyByID)
		covered := categoriesCoveredByPatterns(effective, c.MapsToComponentType, patterns)

		var missing []string
		for _, cat := range c.TypicalHazardCategories {
			if !covered[cat] {
				missing = append(missing, cat)
			}
		}
		if len(missing) == 0 {
			report.Consistent++
			continue
		}

		result := ComponentResult{
			ComponentID:        c.ID,
			NameDE:             c.NameDE,
			DeclaredCategories: c.TypicalHazardCategories,
		}
		for cat := range covered {
			result.CoveredCategories = append(result.CoveredCategories, cat)
		}
		sort.Strings(result.CoveredCategories)
		for _, cat := range missing {
			result.MissingForCategories = append(result.MissingForCategories, CategoryGap{
				Category:      cat,
				SuggestedTags: suggestTagsForCategory(cat, effective, patterns),
			})
		}
		report.Incomplete++
		report.IncompleteComponents = append(report.IncompleteComponents, result)
	}

	sort.Slice(report.IncompleteComponents, func(i, j int) bool {
		return report.IncompleteComponents[i].ComponentID < report.IncompleteComponents[j].ComponentID
	})
	return report
}

func buildEffectiveTags(c iace.ComponentLibraryEntry, energyByID map[string]iace.EnergySourceEntry) map[string]bool {
	set := map[string]bool{}
	for _, t := range c.Tags {
		set[t] = true
	}
	for _, eID := range c.TypicalEnergySources {
		e, ok := energyByID[eID]
		if !ok {
			continue
		}
		for _, t := range e.Tags {
			set[t] = true
		}
	}
	return set
}

// categoriesCoveredByPatterns iterates patterns and finds which
// GeneratedHazardCats can fire given the component's effective tags.
// We ignore lifecycle, op-state, and human-role filters — those are
// project-level. The audit asks "can the library produce ANY hazard in
// this category for this component if the project configures everything
// reasonably?"
func categoriesCoveredByPatterns(tags map[string]bool, _ string, patterns []iace.HazardPattern) map[string]bool {
	covered := map[string]bool{}
	for _, p := range patterns {
		if !tagsCover(tags, p.RequiredComponentTags) {
			continue
		}
		if !tagsCover(tags, p.RequiredEnergyTags) {
			continue
		}
		for _, cat := range p.GeneratedHazardCats {
			covered[cat] = true
		}
	}
	return covered
}

func tagsCover(have map[string]bool, required []string) bool {
	for _, t := range required {
		if !have[t] {
			return false
		}
	}
	return true
}

// suggestTagsForCategory looks at patterns that DO generate this category
// and identifies the tags that would close the gap. Returns the tags most
// commonly required by patterns in that category, minus what the component
// already has.
func suggestTagsForCategory(cat string, have map[string]bool, patterns []iace.HazardPattern) []string {
	counts := map[string]int{}
	for _, p := range patterns {
		matchCat := false
		for _, c := range p.GeneratedHazardCats {
			if c == cat {
				matchCat = true
				break
			}
		}
		if !matchCat {
			continue
		}
		for _, t := range p.RequiredComponentTags {
			if !have[t] {
				counts[t]++
			}
		}
		for _, t := range p.RequiredEnergyTags {
			if !have[t] {
				counts[t]++
			}
		}
	}
	type kv struct {
		tag string
		n   int
	}
	var sorted []kv
	for t, n := range counts {
		sorted = append(sorted, kv{t, n})
	}
	sort.Slice(sorted, func(i, j int) bool { return sorted[i].n > sorted[j].n })
	var out []string
	for i, s := range sorted {
		if i >= 6 {
			break
		}
		out = append(out, s.tag)
	}
	return out
}
