package iace

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
)

// Coverage blind-spot proposer (P2 slice 6, type 4). DEV-TIME, propose-only.
//
// Deterministic skeleton: which EN ISO 12100 hazard groups (A-G, the classic CE
// groups; H-J are control/CRA and routinely routed elsewhere) did the engine
// leave with ZERO hazards for this machine? An empty group is a structural
// blind-spot signal — the machine may genuinely lack that hazard, or a pattern
// may be missing. The LLM then expands each gap into specific expected-but-missing
// hazards a safety assessor would name, for a human to confirm into a new pattern
// or GT case. The gaps alone are useful without any model.

type isoGroup struct {
	Key   string
	Label string
	Cats  []string
}

var iso12100Groups = []isoGroup{
	{"mechanical", "A. Mechanisch", []string{"mechanical_hazard", "mechanical", "maintenance_hazard"}},
	{"electrical", "B. Elektrisch", []string{"electrical_hazard", "electrical", "emc_hazard"}},
	{"thermal", "C. Thermisch", []string{"thermal_hazard", "thermal", "high_temperature", "fire_explosion"}},
	{"pneumatic_hydraulic", "D. Pneumatik/Hydraulik", []string{"pneumatic_hydraulic"}},
	{"noise_vibration", "E. Laerm/Vibration", []string{"noise_hazard", "noise_vibration", "vibration_hazard"}},
	{"ergonomic", "F. Ergonomie", []string{"ergonomic_hazard", "ergonomic"}},
	{"material", "G. Stoffe/Umwelt", []string{"material_environmental", "chemical_risk", "radiation_hazard"}},
}

// CoverageGap is an ISO 12100 hazard group with no engine hazard.
type CoverageGap struct {
	Group string `json:"group"`
	Key   string `json:"key"`
	Note  string `json:"note"`
}

// FindCoverageGaps returns the A-G hazard groups that produced zero hazards.
func FindCoverageGaps(hazards []Hazard) []CoverageGap {
	present := make(map[string]bool, len(hazards))
	for _, h := range hazards {
		present[h.Category] = true
	}
	var gaps []CoverageGap
	for _, g := range iso12100Groups {
		covered := false
		for _, c := range g.Cats {
			if present[c] {
				covered = true
				break
			}
		}
		if !covered {
			gaps = append(gaps, CoverageGap{
				Group: g.Label, Key: g.Key,
				Note: "no engine hazard in this ISO 12100 group — verify the machine truly lacks it, or a pattern is missing",
			})
		}
	}
	return gaps
}

// MissingHazard is an LLM-proposed hazard a safety assessor would expect.
type MissingHazard struct {
	Group  string `json:"group"`
	Hazard string `json:"hazard"`
	Why    string `json:"why"`
}

// ProposeMissingHazards asks the LLM to expand the empty groups into specific
// expected hazards. Returns nil without a completer or on any error — propose-only,
// never breaks the run.
func ProposeMissingHazards(ctx context.Context, completer LLMCompleter, machineClass, narrative string, produced []Hazard, gaps []CoverageGap) []MissingHazard {
	if completer == nil || len(gaps) == 0 {
		return nil
	}
	system, user := BuildCoveragePrompt(machineClass, narrative, produced, gaps)
	raw, err := completer.Complete(ctx, system, user)
	if err != nil {
		return nil
	}
	return parseMissingHazards(raw)
}

// BuildCoveragePrompt frames the "what is missing?" question for the LLM.
func BuildCoveragePrompt(machineClass, narrative string, produced []Hazard, gaps []CoverageGap) (system, user string) {
	system = "Du bist Sachverstaendiger fuer Maschinensicherheit nach EN ISO 12100. " +
		"Dir werden eine Maschine, die bereits erkannten Gefaehrdungen und Gefaehrdungsgruppen OHNE Eintrag genannt. " +
		"Nenne nur Gefaehrdungen, die ein Sachverstaendiger fuer DIESE Maschine ERWARTET, die aber FEHLEN. " +
		"Erfinde nichts Maschinenfremdes. Antworte AUSSCHLIESSLICH als JSON-Array: " +
		`[{"group":"...","hazard":"...","why":"..."}].`

	var have []string
	seen := map[string]bool{}
	for _, h := range produced {
		if h.Category != "" && !seen[h.Category] {
			seen[h.Category] = true
			have = append(have, h.Category)
		}
	}
	var empty []string
	for _, g := range gaps {
		empty = append(empty, g.Group)
	}
	user = fmt.Sprintf("Maschinenklasse: %s\n\nBeschreibung:\n%s\n\nBereits erkannte Kategorien: %s\n\nGruppen OHNE Eintrag (Fokus): %s\n\nWelche erwarteten Gefaehrdungen fehlen?",
		machineClass, narrative, strings.Join(have, ", "), strings.Join(empty, ", "))
	return system, user
}

func parseMissingHazards(raw string) []MissingHazard {
	start, end := strings.Index(raw, "["), strings.LastIndex(raw, "]")
	if start < 0 || end <= start {
		return nil
	}
	var out []MissingHazard
	if err := json.Unmarshal([]byte(raw[start:end+1]), &out); err != nil {
		return nil
	}
	return out
}

// RenderCoverageQueue renders the deterministic gaps plus any LLM-proposed missing
// hazards as a markdown review queue.
func RenderCoverageQueue(machine string, gaps []CoverageGap, missing []MissingHazard) string {
	var b strings.Builder
	fmt.Fprintf(&b, "# Coverage blind-spot queue — %s\n\n", machine)
	fmt.Fprintf(&b, "%d ISO 12100 group(s) (A-G) have no engine hazard. Propose-only — a human confirms whether the machine truly lacks it or a pattern/GT case is missing.\n\n", len(gaps))
	for _, g := range gaps {
		fmt.Fprintf(&b, "- **%s** — %s\n", g.Group, g.Note)
	}
	if len(missing) > 0 {
		fmt.Fprintf(&b, "\n## LLM-proposed expected-but-missing hazards (%d)\n\n", len(missing))
		for i, m := range missing {
			fmt.Fprintf(&b, "%d. [%s] %s\n   - why: %s\n", i+1, m.Group, m.Hazard, m.Why)
		}
	}
	return b.String()
}
