package iace

import (
	"context"
	"fmt"
	"strings"

	"github.com/breakpilot/ai-compliance-sdk/internal/llm"
	"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
	"github.com/google/uuid"
)

// ============================================================================
// TechFileGenerator — LLM-based generation of technical file sections
// ============================================================================

// TechFileGenerator generates technical file section content using LLM and RAG.
type TechFileGenerator struct {
	llmRegistry *llm.ProviderRegistry
	ragClient   *ucca.LegalRAGClient
	store       *Store
}

// NewTechFileGenerator creates a new TechFileGenerator.
func NewTechFileGenerator(registry *llm.ProviderRegistry, ragClient *ucca.LegalRAGClient, store *Store) *TechFileGenerator {
	return &TechFileGenerator{
		llmRegistry: registry,
		ragClient:   ragClient,
		store:       store,
	}
}

// SectionGenerationContext holds all project data needed for LLM section generation.
type SectionGenerationContext struct {
	Project         *Project
	Components      []Component
	Hazards         []Hazard
	Assessments     map[uuid.UUID][]RiskAssessment // keyed by hazardID
	Mitigations     map[uuid.UUID][]Mitigation     // keyed by hazardID
	Classifications []RegulatoryClassification
	Evidence        []Evidence
	RAGContext      string // aggregated text from RAG search
}

// ============================================================================
// BuildSectionContext — loads all project data + RAG context
// ============================================================================

// BuildSectionContext loads project data and RAG context for a given section type.
func (g *TechFileGenerator) BuildSectionContext(ctx context.Context, projectID uuid.UUID, sectionType string) (*SectionGenerationContext, error) {
	// Load project
	project, err := g.store.GetProject(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("load project: %w", err)
	}
	if project == nil {
		return nil, fmt.Errorf("project %s not found", projectID)
	}

	// Load components
	components, err := g.store.ListComponents(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("load components: %w", err)
	}

	// Load hazards
	hazards, err := g.store.ListHazards(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("load hazards: %w", err)
	}

	// Load assessments and mitigations per hazard
	assessments := make(map[uuid.UUID][]RiskAssessment)
	mitigations := make(map[uuid.UUID][]Mitigation)
	for _, h := range hazards {
		a, err := g.store.ListAssessments(ctx, h.ID)
		if err != nil {
			return nil, fmt.Errorf("load assessments for hazard %s: %w", h.ID, err)
		}
		assessments[h.ID] = a

		m, err := g.store.ListMitigations(ctx, h.ID)
		if err != nil {
			return nil, fmt.Errorf("load mitigations for hazard %s: %w", h.ID, err)
		}
		mitigations[h.ID] = m
	}

	// Load classifications
	classifications, err := g.store.GetClassifications(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("load classifications: %w", err)
	}

	// Load evidence
	evidence, err := g.store.ListEvidence(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("load evidence: %w", err)
	}

	// Perform RAG search for section-specific context
	ragContext := ""
	if g.ragClient != nil {
		ragQuery := buildRAGQuery(sectionType)
		results, ragErr := g.ragClient.SearchCollection(ctx, "bp_iace_libraries", ragQuery, nil, 5)
		if ragErr == nil && len(results) > 0 {
			var ragParts []string
			for _, r := range results {
				entry := fmt.Sprintf("[%s] %s", r.RegulationShort, truncateForPrompt(r.Text, 400))
				ragParts = append(ragParts, entry)
			}
			ragContext = strings.Join(ragParts, "\n\n")
		}
		// RAG failure is non-fatal — we proceed without context
	}

	return &SectionGenerationContext{
		Project:         project,
		Components:      components,
		Hazards:         hazards,
		Assessments:     assessments,
		Mitigations:     mitigations,
		Classifications: classifications,
		Evidence:        evidence,
		RAGContext:      ragContext,
	}, nil
}

// ============================================================================
// GenerateSection — main entry point
// ============================================================================

// GenerateSection generates the content for a technical file section using LLM.
// If LLM is unavailable, returns an enhanced placeholder with project data.
func (g *TechFileGenerator) GenerateSection(ctx context.Context, projectID uuid.UUID, sectionType string) (string, error) {
	sctx, err := g.BuildSectionContext(ctx, projectID, sectionType)
	if err != nil {
		return "", fmt.Errorf("build section context: %w", err)
	}

	// Build prompts
	systemPrompt := getSystemPrompt(sectionType)
	userPrompt := buildUserPrompt(sctx, sectionType)

	// Attempt LLM generation
	resp, err := g.llmRegistry.Chat(ctx, &llm.ChatRequest{
		Messages: []llm.Message{
			{Role: "system", Content: systemPrompt},
			{Role: "user", Content: userPrompt},
		},
		Temperature: 0.15,
		MaxTokens:   4096,
	})
	if err != nil {
		// LLM unavailable — return structured fallback with real project data
		return appendCrossRefIfApplicable(buildFallbackContent(sctx, sectionType), sctx, sectionType), nil
	}

	return appendCrossRefIfApplicable(resp.Message.Content, sctx, sectionType), nil
}

// appendCrossRefIfApplicable adds the international cross-reference appendix
// (DIN/ANSI/GB/JIS) to the "standards_applied" section. For other section
// types it returns content unchanged. The appendix is built deterministically
// from the in-process registry, so it is never hallucinated by the LLM.
func appendCrossRefIfApplicable(content string, sctx *SectionGenerationContext, sectionType string) string {
	if sectionType != SectionStandardsApplied {
		return content
	}
	normIDs := suggestNormIDsForProject(sctx)
	appendix := RenderCrossRefAppendix(normIDs)
	if appendix == "" {
		return content
	}
	return content + appendix
}

// suggestNormIDsForProject reuses the existing SuggestNorms heuristic to pick
// the norms most likely applicable to this project. We only need the IDs;
// the rest of the SuggestNorms output (scores, reasons) is discarded.
func suggestNormIDsForProject(sctx *SectionGenerationContext) []string {
	if sctx == nil || sctx.Project == nil {
		return nil
	}
	hazardCats := make([]string, 0, len(sctx.Hazards))
	seenCat := map[string]bool{}
	for _, h := range sctx.Hazards {
		if h.Category != "" && !seenCat[h.Category] {
			seenCat[h.Category] = true
			hazardCats = append(hazardCats, h.Category)
		}
	}
	result := SuggestNorms(sctx.Project.MachineType, hazardCats, nil)
	if result == nil {
		return nil
	}
	ids := make([]string, 0, result.Total)
	seenID := map[string]bool{}
	push := func(suggs []NormSuggestion) {
		for _, s := range suggs {
			if s.Norm.ID == "" || seenID[s.Norm.ID] {
				continue
			}
			seenID[s.Norm.ID] = true
			ids = append(ids, s.Norm.ID)
		}
	}
	push(result.ANorms)
	push(result.B1Norms)
	push(result.B2Norms)
	push(result.CNorms)
	return ids
}
