package iace

import (
	"strings"

	"github.com/google/uuid"
)

// Clarification represents an aggregated open question that the operator
// must verify with the Anlagenbauer. The engine NEVER generates commentary
// — it only surfaces norm-/manufacturer-derived check items that can be
// objectively answered.
//
// IDs are deterministic so existing answers survive every project re-init:
//   - pattern:<HP-ID>:<index>     — question is hard-coded on a HazardPattern
//   - manuf:<Manufacturer>:<index> — question comes from the manufacturer library
//
// "AffectedHazardIDs" / "AffectedMitigationIDs" are filled at request time
// from the project's current hazards. They tell the UI which entries in the
// hazard list will be marked "geklaert" once this clarification is answered.
type Clarification struct {
	ID                    string      `json:"id"`
	Question              string      `json:"question"`
	Source                string      `json:"source"`                 // "FANUC (Dual Check Safety)", "Pattern HP1640", ...
	Category              string      `json:"category"`               // "manufacturer" | "pattern_norm"
	NormReferences        []string    `json:"norm_references,omitempty"`
	AffectedHazardIDs     []uuid.UUID `json:"affected_hazard_ids"`
	AffectedHazardNames   []string    `json:"affected_hazard_names"`  // shown directly in the table
	AffectedMitigationIDs []uuid.UUID `json:"affected_mitigation_ids,omitempty"`
	// State (merged from project.metadata.clarification_answers)
	Status    string `json:"status"`              // "open" | "in_progress" | "answered" | "not_relevant"
	Answer    string `json:"answer,omitempty"`    // "ja" | "nein" | "teilweise"
	Reasoning string `json:"reasoning,omitempty"`
	AnsweredBy string `json:"answered_by,omitempty"`
	AnsweredAt string `json:"answered_at,omitempty"`
	AssignedTo string `json:"assigned_to,omitempty"`
}

// ClarificationAnswer is the persisted shape (one entry in
// project.metadata.clarification_answers[<clarification.id>]).
type ClarificationAnswer struct {
	Status     string `json:"status"`
	Answer     string `json:"answer,omitempty"`
	Reasoning  string `json:"reasoning,omitempty"`
	AnsweredBy string `json:"answered_by,omitempty"`
	AnsweredAt string `json:"answered_at,omitempty"`
	AssignedTo string `json:"assigned_to,omitempty"`
}

// BuildProjectClarifications walks the project's current hazards and returns
// the deduplicated list of clarification questions that apply, with each
// hazard correctly cross-referenced.
//
// Inputs are resolved upstream so this function stays free of DB access and
// is unit-testable:
//   - hazards: the project's persisted hazards (Name, ID, Category)
//   - hazardSourcePatterns: per hazard, the HP-IDs that fired for it (today
//     we don't have a clean back-reference, so the handler does a name+zone
//     re-match against patterns)
//   - manufacturerHits: ManufacturerSafetyFeature entries whose aliases were
//     found in the project narrative
//   - answers: map[clarificationID]ClarificationAnswer from project.metadata
func BuildProjectClarifications(
	hazards []Hazard,
	hazardSourcePatterns map[uuid.UUID][]string,
	manufacturerHits []ManufacturerSafetyFeature,
	answers map[string]ClarificationAnswer,
) []Clarification {
	// Lookup helpers
	patternByID := make(map[string]HazardPattern)
	for _, p := range collectAllPatterns() {
		patternByID[p.ID] = p
	}

	// Bucket by clarification ID so we accumulate affected hazards
	buckets := make(map[string]*Clarification)

	// 1) Pattern-level clarifications
	for hzID, hpIDs := range hazardSourcePatterns {
		hz := findHazard(hazards, hzID)
		if hz == nil {
			continue
		}
		for _, hpID := range hpIDs {
			p, ok := patternByID[hpID]
			if !ok {
				continue
			}
			for i, q := range p.ClarificationQuestionsDE {
				cid := "pattern:" + hpID + ":" + intStr(i)
				b, exists := buckets[cid]
				if !exists {
					b = &Clarification{
						ID:       cid,
						Question: q,
						Source:   "Pattern " + hpID + " — " + p.NameDE,
						Category: "pattern_norm",
						Status:   "open",
					}
					buckets[cid] = b
				}
				b.AffectedHazardIDs = append(b.AffectedHazardIDs, hz.ID)
				b.AffectedHazardNames = appendUniqueString(b.AffectedHazardNames, hz.Name)
			}
		}
	}

	// 2) Manufacturer-level clarifications — apply to every hazard whose
	//    category matches the manufacturer entry's AppliesToHazardCats
	for _, mf := range manufacturerHits {
		applicable := func(cat string) bool {
			if len(mf.AppliesToHazardCats) == 0 {
				return true
			}
			for _, c := range mf.AppliesToHazardCats {
				if c == cat {
					return true
				}
			}
			return false
		}
		for i, q := range mf.Clarifications {
			cid := "manuf:" + slug(mf.Manufacturer) + ":" + slug(mf.FeatureName) + ":" + intStr(i)
			b, exists := buckets[cid]
			if !exists {
				b = &Clarification{
					ID:             cid,
					Question:       q,
					Source:         mf.Manufacturer + " — " + mf.FeatureName,
					Category:       "manufacturer",
					NormReferences: mf.NormReferences,
					Status:         "open",
				}
				buckets[cid] = b
			}
			for _, hz := range hazards {
				if !applicable(hz.Category) {
					continue
				}
				b.AffectedHazardIDs = append(b.AffectedHazardIDs, hz.ID)
				b.AffectedHazardNames = appendUniqueString(b.AffectedHazardNames, hz.Name)
			}
		}
	}

	// Merge persisted answers
	out := make([]Clarification, 0, len(buckets))
	for cid, b := range buckets {
		if ans, ok := answers[cid]; ok {
			if ans.Status != "" {
				b.Status = ans.Status
			} else if ans.Answer != "" {
				b.Status = "answered"
			}
			b.Answer = ans.Answer
			b.Reasoning = ans.Reasoning
			b.AnsweredBy = ans.AnsweredBy
			b.AnsweredAt = ans.AnsweredAt
			b.AssignedTo = ans.AssignedTo
		}
		// dedup hazard IDs (multiple patterns can target the same hazard)
		b.AffectedHazardIDs = dedupUUIDs(b.AffectedHazardIDs)
		out = append(out, *b)
	}
	return out
}

func findHazard(hazards []Hazard, id uuid.UUID) *Hazard {
	for i := range hazards {
		if hazards[i].ID == id {
			return &hazards[i]
		}
	}
	return nil
}

func appendUniqueString(slice []string, s string) []string {
	for _, x := range slice {
		if x == s {
			return slice
		}
	}
	return append(slice, s)
}

func dedupUUIDs(ids []uuid.UUID) []uuid.UUID {
	seen := make(map[uuid.UUID]bool, len(ids))
	out := make([]uuid.UUID, 0, len(ids))
	for _, id := range ids {
		if !seen[id] {
			seen[id] = true
			out = append(out, id)
		}
	}
	return out
}

func intStr(i int) string {
	if i == 0 {
		return "0"
	}
	neg := false
	if i < 0 {
		neg = true
		i = -i
	}
	var buf [20]byte
	pos := len(buf)
	for i > 0 {
		pos--
		buf[pos] = byte('0' + i%10)
		i /= 10
	}
	if neg {
		pos--
		buf[pos] = '-'
	}
	return string(buf[pos:])
}

// slug lowercases and replaces non-[a-z0-9] with "-" so the manufacturer name
// and feature name can be embedded in a stable clarification ID.
func slug(s string) string {
	s = normalizeForMatch(s) // already lower + umlaut-folded
	var b strings.Builder
	prevDash := false
	for _, r := range s {
		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
			b.WriteRune(r)
			prevDash = false
		} else {
			if !prevDash && b.Len() > 0 {
				b.WriteRune('-')
				prevDash = true
			}
		}
	}
	out := b.String()
	if strings.HasSuffix(out, "-") {
		out = out[:len(out)-1]
	}
	return out
}
