package training

import (
	"context"
	"fmt"
	"math"

	"github.com/google/uuid"
)

// BlockGenerator orchestrates the Controls → Training Modules pipeline
type BlockGenerator struct {
	store            *Store
	contentGenerator *ContentGenerator
}

// NewBlockGenerator creates a new block generator
func NewBlockGenerator(store *Store, contentGenerator *ContentGenerator) *BlockGenerator {
	return &BlockGenerator{
		store:            store,
		contentGenerator: contentGenerator,
	}
}

// Preview performs a dry run: loads matching controls, computes module split and roles
func (bg *BlockGenerator) Preview(ctx context.Context, configID uuid.UUID) (*PreviewBlockResponse, error) {
	config, err := bg.store.GetBlockConfig(ctx, configID)
	if err != nil {
		return nil, fmt.Errorf("load block config: %w", err)
	}
	if config == nil {
		return nil, fmt.Errorf("block config not found")
	}

	controls, err := bg.store.QueryCanonicalControls(ctx,
		config.DomainFilter, config.CategoryFilter,
		config.SeverityFilter, config.TargetAudienceFilter,
	)
	if err != nil {
		return nil, fmt.Errorf("query controls: %w", err)
	}

	maxPerModule := config.MaxControlsPerModule
	if maxPerModule <= 0 {
		maxPerModule = 20
	}
	moduleCount := int(math.Ceil(float64(len(controls)) / float64(maxPerModule)))
	if moduleCount == 0 && len(controls) > 0 {
		moduleCount = 1
	}

	roles := bg.deriveRoles(controls, config.TargetAudienceFilter)

	return &PreviewBlockResponse{
		ControlCount:  len(controls),
		ModuleCount:   moduleCount,
		Controls:      controls,
		ProposedRoles: roles,
	}, nil
}

// Generate executes the full pipeline: Controls → Modules → Links → CTM → Content
func (bg *BlockGenerator) Generate(ctx context.Context, configID uuid.UUID, req GenerateBlockRequest) (*GenerateBlockResponse, error) {
	config, err := bg.store.GetBlockConfig(ctx, configID)
	if err != nil {
		return nil, fmt.Errorf("load block config: %w", err)
	}
	if config == nil {
		return nil, fmt.Errorf("block config not found")
	}

	// 1. Load matching controls
	controls, err := bg.store.QueryCanonicalControls(ctx,
		config.DomainFilter, config.CategoryFilter,
		config.SeverityFilter, config.TargetAudienceFilter,
	)
	if err != nil {
		return nil, fmt.Errorf("query controls: %w", err)
	}

	if len(controls) == 0 {
		return &GenerateBlockResponse{}, nil
	}

	// 2. Chunk controls into module-sized groups
	maxPerModule := config.MaxControlsPerModule
	if maxPerModule <= 0 {
		maxPerModule = 20
	}
	chunks := chunkControls(controls, maxPerModule)

	// 3. Derive target roles for CTM
	roles := bg.deriveRoles(controls, config.TargetAudienceFilter)

	// 4. Count existing modules with this prefix for auto-numbering
	existingCount, err := bg.store.CountModulesWithPrefix(ctx, config.TenantID, config.ModuleCodePrefix)
	if err != nil {
		existingCount = 0
	}

	language := req.Language
	if language == "" {
		language = "de"
	}

	resp := &GenerateBlockResponse{}

	for i, chunk := range chunks {
		moduleNum := existingCount + i + 1
		moduleCode := fmt.Sprintf("%s-%02d", config.ModuleCodePrefix, moduleNum)

		// Build a descriptive title from the first few controls
		title := bg.buildModuleTitle(config, chunk, i+1, len(chunks))

		// a. Create TrainingModule
		module := &TrainingModule{
			TenantID:        config.TenantID,
			ModuleCode:      moduleCode,
			Title:           title,
			Description:     config.Description,
			RegulationArea:  config.RegulationArea,
			NIS2Relevant:    config.RegulationArea == RegulationNIS2,
			ISOControls:     bg.extractControlIDs(chunk),
			FrequencyType:   config.FrequencyType,
			ValidityDays:    365,
			RiskWeight:      2.0,
			ContentType:     "text",
			DurationMinutes: config.DurationMinutes,
			PassThreshold:   config.PassThreshold,
			IsActive:        true,
			SortOrder:       moduleNum,
		}

		if err := bg.store.CreateModule(ctx, module); err != nil {
			resp.Errors = append(resp.Errors, fmt.Sprintf("create module %s: %v", moduleCode, err))
			continue
		}
		resp.ModulesCreated++

		// b. Create control links (traceability)
		for j, ctrl := range chunk {
			link := &TrainingBlockControlLink{
				BlockConfigID:       config.ID,
				ModuleID:            module.ID,
				ControlID:           ctrl.ControlID,
				ControlTitle:        ctrl.Title,
				ControlObjective:    ctrl.Objective,
				ControlRequirements: ctrl.Requirements,
				SortOrder:           j,
			}
			if err := bg.store.CreateBlockControlLink(ctx, link); err != nil {
				resp.Errors = append(resp.Errors, fmt.Sprintf("link %s→%s: %v", moduleCode, ctrl.ControlID, err))
				continue
			}
			resp.ControlsLinked++
		}

		// c. Create CTM entries (target_audience → roles)
		if req.AutoMatrix {
			for _, role := range roles {
				entry := &TrainingMatrixEntry{
					TenantID:    config.TenantID,
					RoleCode:    role,
					ModuleID:    module.ID,
					IsMandatory: true,
					Priority:    1,
				}
				if err := bg.store.SetMatrixEntry(ctx, entry); err != nil {
					resp.Errors = append(resp.Errors, fmt.Sprintf("matrix %s→%s: %v", role, moduleCode, err))
					continue
				}
				resp.MatrixEntriesCreated++
			}
		}

		// d. Generate LLM content
		_, err := bg.contentGenerator.GenerateBlockContent(ctx, *module, chunk, language)
		if err != nil {
			resp.Errors = append(resp.Errors, fmt.Sprintf("content %s: %v", moduleCode, err))
			continue
		}
		resp.ContentGenerated++
	}

	// 5. Update last_generated_at
	bg.store.UpdateBlockConfigLastGenerated(ctx, config.ID)

	// 6. Audit log
	bg.store.LogAction(ctx, &AuditLogEntry{
		TenantID:   config.TenantID,
		Action:     AuditAction("block_generated"),
		EntityType: AuditEntityModule,
		Details: map[string]interface{}{
			"block_config_id":  config.ID.String(),
			"block_name":       config.Name,
			"modules_created":  resp.ModulesCreated,
			"controls_linked":  resp.ControlsLinked,
			"content_generated": resp.ContentGenerated,
		},
	})

	return resp, nil
}

// deriveRoles computes which CTM roles should receive the generated modules
func (bg *BlockGenerator) deriveRoles(controls []CanonicalControlSummary, audienceFilter string) []string {
	roleSet := map[string]bool{}

	// If a specific audience filter is set, use the mapping
	if audienceFilter != "" {
		if roles, ok := TargetAudienceRoleMapping[audienceFilter]; ok {
			for _, r := range roles {
				roleSet[r] = true
			}
		}
	}

	// Additionally derive roles from control categories
	for _, ctrl := range controls {
		if ctrl.Category != "" {
			if roles, ok := CategoryRoleMapping[ctrl.Category]; ok {
				for _, r := range roles {
					roleSet[r] = true
				}
			}
		}
		// Also check per-control target_audience
		if ctrl.TargetAudience != "" && audienceFilter == "" {
			if roles, ok := TargetAudienceRoleMapping[ctrl.TargetAudience]; ok {
				for _, r := range roles {
					roleSet[r] = true
				}
			}
		}
	}

	// If nothing derived, default to R9 (Alle Mitarbeiter)
	if len(roleSet) == 0 {
		roleSet[RoleR9] = true
	}

	roles := make([]string, 0, len(roleSet))
	for r := range roleSet {
		roles = append(roles, r)
	}
	return roles
}

// buildModuleTitle creates a descriptive module title
func (bg *BlockGenerator) buildModuleTitle(config *TrainingBlockConfig, controls []CanonicalControlSummary, partNum, totalParts int) string {
	base := config.Name
	if totalParts > 1 {
		base = fmt.Sprintf("%s (Teil %d/%d)", config.Name, partNum, totalParts)
	}
	return base
}

// extractControlIDs returns the control IDs from a slice of controls
func (bg *BlockGenerator) extractControlIDs(controls []CanonicalControlSummary) []string {
	ids := make([]string, len(controls))
	for i, c := range controls {
		ids[i] = c.ControlID
	}
	return ids
}

// chunkControls splits controls into groups of maxSize
func chunkControls(controls []CanonicalControlSummary, maxSize int) [][]CanonicalControlSummary {
	if maxSize <= 0 {
		maxSize = 20
	}

	var chunks [][]CanonicalControlSummary
	for i := 0; i < len(controls); i += maxSize {
		end := i + maxSize
		if end > len(controls) {
			end = len(controls)
		}
		chunks = append(chunks, controls[i:end])
	}
	return chunks
}
