package mapping

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	"qctextbuilder/internal/domain"
)

type SuggestionRequest struct {
	TemplateID    int64
	DraftID       string
	Fields        []domain.TemplateField
	GlobalData    map[string]any
	DraftContext  *domain.DraftContext
	MasterPrompt  string
	PromptBlocks  []domain.PromptBlockConfig
	Existing      map[string]string
	IncludeFilled bool
}

type Suggestion struct {
	FieldPath string `json:"fieldPath"`
	Slot      string `json:"slot,omitempty"`
	Value     string `json:"value"`
	Reason    string `json:"reason,omitempty"`
	Source    string `json:"source,omitempty"`
}

type SuggestionResult struct {
	Suggestions []Suggestion          `json:"suggestions"`
	ByFieldPath map[string]Suggestion `json:"byFieldPath"`
}

func SuggestFieldValues(req SuggestionRequest) SuggestionResult {
	return SuggestFieldValuesRuleBased(req)
}

func SuggestFieldValuesRuleBased(req SuggestionRequest) SuggestionResult {
	existing := req.Existing
	if existing == nil {
		existing = map[string]string{}
	}
	mappingResult := MapTemplateFieldsToSemanticSlots(req.Fields)
	ctx := suggestionContextFrom(req.GlobalData, req.DraftContext)

	out := SuggestionResult{
		Suggestions: make([]Suggestion, 0),
		ByFieldPath: map[string]Suggestion{},
	}
	seen := map[string]struct{}{}

	targets := append([]SemanticSlotTarget(nil), mappingResult.Targets...)
	sort.SliceStable(targets, func(i, j int) bool {
		if targets[i].FieldPath == targets[j].FieldPath {
			return targets[i].Slot < targets[j].Slot
		}
		return targets[i].FieldPath < targets[j].FieldPath
	})

	for _, target := range targets {
		if _, ok := seen[target.FieldPath]; ok {
			continue
		}
		if !req.IncludeFilled && strings.TrimSpace(existing[target.FieldPath]) != "" {
			continue
		}
		value, reason, ok := suggestValueForSlot(target.Slot, ctx)
		if !ok || strings.TrimSpace(value) == "" {
			continue
		}
		suggestion := Suggestion{
			FieldPath: target.FieldPath,
			Slot:      target.Slot,
			Value:     value,
			Reason:    reason,
			Source:    domain.DraftSuggestionSourceFallbackRuleBased,
		}
		out.Suggestions = append(out.Suggestions, suggestion)
		out.ByFieldPath[target.FieldPath] = suggestion
		seen[target.FieldPath] = struct{}{}
	}

	return out
}

type suggestionContext struct {
	CompanyName      string
	BusinessType     string
	WebsiteSummary   string
	LocaleStyle      string
	MarketStyle      string
	AddressMode      string
	ContentTone      string
	PromptNote       string
	DescriptionShort string
	DescriptionLong  string
	Mission          string
}

func suggestionContextFrom(globalData map[string]any, draftContext *domain.DraftContext) suggestionContext {
	ctx := suggestionContext{
		CompanyName:      getMapString(globalData, "companyName"),
		BusinessType:     getMapString(globalData, "businessType"),
		DescriptionShort: getMapString(globalData, "descriptionShort"),
		DescriptionLong:  getMapString(globalData, "descriptionLong"),
		Mission:          getMapString(globalData, "mission"),
	}
	if draftContext == nil {
		return ctx
	}
	if strings.TrimSpace(ctx.BusinessType) == "" {
		ctx.BusinessType = strings.TrimSpace(draftContext.LLM.BusinessType)
	}
	ctx.WebsiteSummary = strings.TrimSpace(draftContext.LLM.WebsiteSummary)
	ctx.LocaleStyle = strings.TrimSpace(draftContext.LLM.StyleProfile.LocaleStyle)
	ctx.MarketStyle = strings.TrimSpace(draftContext.LLM.StyleProfile.MarketStyle)
	ctx.AddressMode = strings.TrimSpace(draftContext.LLM.StyleProfile.AddressMode)
	ctx.ContentTone = strings.TrimSpace(draftContext.LLM.StyleProfile.ContentTone)
	ctx.PromptNote = strings.TrimSpace(draftContext.LLM.StyleProfile.PromptInstructions)
	return ctx
}

func suggestValueForSlot(slot string, ctx suggestionContext) (string, string, bool) {
	company := fallback(ctx.CompanyName, "Ihr Unternehmen")
	business := fallback(ctx.BusinessType, "Angebot")
	toneAdj := toneAdjective(ctx.ContentTone)
	audienceLine := audienceFlavor(ctx)

	switch {
	case slot == "hero.title":
		return strings.TrimSpace(fmt.Sprintf("%s fuer %s mit %s Klarheit", company, business, toneAdj)), "slot-based hero headline", true
	case slot == "intro.title":
		return strings.TrimSpace(fmt.Sprintf("Was %s fuer Sie einfacher macht", company)), "slot-based intro title", true
	case slot == "intro.description":
		return firstNonEmpty(
			shortenSentence(ctx.WebsiteSummary, 180),
			fmt.Sprintf("%s unterstuetzt Kunden mit %s Leistungen, klarer Kommunikation und einem %s Auftritt.", company, business, toneAdj),
		), "slot-based intro description", true
	case slot == "about.description":
		return firstNonEmpty(
			shortenSentence(ctx.DescriptionLong, 260),
			shortenSentence(ctx.Mission, 220),
			fmt.Sprintf("%s steht fuer %s, verlaessliche Zusammenarbeit und einen %s Anspruch in Beratung und Umsetzung.", company, business, toneAdj),
		), "slot-based about description", true
	case strings.HasPrefix(slot, "service_items[") && strings.HasSuffix(slot, "].title"):
		idx := repeatedSlotIndex(slot)
		return fmt.Sprintf("%s Leistung %d", titleCaseBusiness(business), idx+1), "slot-based service title", true
	case strings.HasPrefix(slot, "service_items[") && strings.HasSuffix(slot, "].description"):
		idx := repeatedSlotIndex(slot)
		return fmt.Sprintf("Praezise Umsetzung von %s mit Fokus auf Nutzen, Verstaendlichkeit und %s Wirkung%s.", business, toneAdj, audienceLineForIndex(audienceLine, idx)), "slot-based service description", true
	case strings.HasPrefix(slot, "team_items[") && strings.HasSuffix(slot, "].name"):
		idx := repeatedSlotIndex(slot)
		return fmt.Sprintf("Ansprechperson %d", idx+1), "slot-based team placeholder", true
	case strings.HasPrefix(slot, "team_items[") && strings.HasSuffix(slot, "].description"):
		idx := repeatedSlotIndex(slot)
		return fmt.Sprintf("Begleitet Projekte bei %s mit fachlicher Sicherheit, klarer Kommunikation und %s Auftreten.", business, toneAdjForIndex(toneAdj, idx)), "slot-based team description", true
	case strings.HasPrefix(slot, "testimonial_items[") && strings.HasSuffix(slot, "].name"):
		idx := repeatedSlotIndex(slot)
		return fmt.Sprintf("Kundin/Kunde %d", idx+1), "slot-based testimonial placeholder", true
	case strings.HasPrefix(slot, "testimonial_items[") && strings.HasSuffix(slot, "].title"):
		return firstNonEmpty(
			testimonialLead(ctx),
			"Vertrauen durch saubere Zusammenarbeit",
		), "slot-based testimonial title", true
	case strings.HasPrefix(slot, "testimonial_items[") && strings.HasSuffix(slot, "].description"):
		return fmt.Sprintf("%s ueberzeugt mit %s Prozessen, klaren Ergebnissen und einer Zusammenarbeit, die angenehm effizient bleibt.", company, toneAdj), "slot-based testimonial description", true
	case slot == "cta.text":
		if ctx.AddressMode == "du" {
			return "Jetzt unverbindlich anfragen", "slot-based cta", true
		}
		return "Jetzt unverbindlich anfragen", "slot-based cta", true
	default:
		return "", "", false
	}
}

func getMapString(values map[string]any, key string) string {
	if values == nil {
		return ""
	}
	raw, _ := values[key].(string)
	return strings.TrimSpace(raw)
}

func fallback(value, alt string) string {
	if strings.TrimSpace(value) == "" {
		return alt
	}
	return strings.TrimSpace(value)
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}

func shortenSentence(value string, max int) string {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" || max <= 0 {
		return ""
	}
	if len([]rune(trimmed)) <= max {
		return trimmed
	}
	runes := []rune(trimmed)
	return strings.TrimSpace(string(runes[:max])) + "..."
}

func repeatedSlotIndex(slot string) int {
	start := strings.Index(slot, "[")
	end := strings.Index(slot, "]")
	if start < 0 || end <= start+1 {
		return 0
	}
	value := strings.TrimSpace(slot[start+1 : end])
	var idx int
	_, _ = fmt.Sscanf(value, "%d", &idx)
	if idx < 0 {
		return 0
	}
	return idx
}

func toneAdjective(tone string) string {
	switch strings.ToLower(strings.TrimSpace(tone)) {
	case "locker":
		return "lockerer"
	case "modern":
		return "moderner"
	case "premium":
		return "hochwertiger"
	case "freundlich":
		return "freundlicher"
	case "professionell":
		return "professioneller"
	default:
		return "klarer"
	}
}

func toneAdjForIndex(adj string, idx int) string {
	if idx%2 == 0 {
		return adj
	}
	return adj
}

func audienceFlavor(ctx suggestionContext) string {
	parts := make([]string, 0, 2)
	if strings.TrimSpace(ctx.LocaleStyle) != "" {
		parts = append(parts, ctx.LocaleStyle)
	}
	if strings.TrimSpace(ctx.MarketStyle) != "" {
		parts = append(parts, ctx.MarketStyle)
	}
	return strings.Join(parts, " / ")
}

func audienceLineForIndex(value string, idx int) string {
	if strings.TrimSpace(value) == "" || idx != 0 {
		return ""
	}
	return " fuer " + value
}

func titleCaseBusiness(value string) string {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "Service"
	}
	return strings.ToUpper(string([]rune(trimmed)[0])) + string([]rune(trimmed)[1:])
}

func testimonialLead(ctx suggestionContext) string {
	if strings.TrimSpace(ctx.WebsiteSummary) == "" {
		return ""
	}
	return shortenSentence(ctx.WebsiteSummary, 80)
}

func GenerateAllSuggestions(ctx context.Context, generator SuggestionGenerator, req SuggestionRequest, current domain.DraftSuggestionState, now time.Time) domain.DraftSuggestionState {
	next := cloneSuggestionState(current)
	if next.ByFieldPath == nil {
		next.ByFieldPath = map[string]domain.DraftSuggestion{}
	}
	generated, err := suggestionResultWithFallback(ctx, generator, SuggestionRequest{
		TemplateID:    req.TemplateID,
		DraftID:       req.DraftID,
		Fields:        req.Fields,
		GlobalData:    req.GlobalData,
		DraftContext:  req.DraftContext,
		MasterPrompt:  req.MasterPrompt,
		PromptBlocks:  req.PromptBlocks,
		Existing:      req.Existing,
		IncludeFilled: true,
	})
	if err != nil {
		return next
	}
	mappingLogger().InfoContext(ctx, "autofill state transition",
		"component", "autofill",
		"step", "post_generate_result",
		"action", "generate_all",
		"generated_count", len(generated.ByFieldPath),
		"generated_sources", summarizeResultSources(generated),
	)
	mappingLogger().DebugContext(ctx, "autofill state transition",
		"component", "autofill",
		"step", "post_generate_result",
		"action", "generate_all",
		"sample_sources", sampleResultSources(generated, 5),
	)
	for _, s := range generated.Suggestions {
		sliceSource := strings.TrimSpace(s.Source)
		canonicalSource := ""
		if canonical, ok := generated.ByFieldPath[strings.TrimSpace(s.FieldPath)]; ok {
			canonicalSource = strings.TrimSpace(canonical.Source)
			s = canonical
		}
		if existing, exists := next.ByFieldPath[s.FieldPath]; exists {
			if !shouldReplaceExistingSuggestion(existing, s) {
				continue
			}
		}
		stored := toDraftSuggestion(s, now)
		if explicitSource := strings.TrimSpace(s.Source); explicitSource != "" {
			stored.Source = explicitSource
		}
		mappingLogger().DebugContext(ctx, "autofill state transition",
			"component", "autofill",
			"step", "apply_field_transition",
			"action", "generate_all",
			"field_path", strings.TrimSpace(s.FieldPath),
			"slice_source", firstNonEmpty(sliceSource, "unknown"),
			"canonical_source", firstNonEmpty(canonicalSource, "unknown"),
			"stored_source", firstNonEmpty(strings.TrimSpace(stored.Source), "unknown"),
		)
		next.ByFieldPath[s.FieldPath] = stored
	}
	mappingLogger().InfoContext(ctx, "autofill state transition",
		"component", "autofill",
		"step", "post_generate_apply_state",
		"action", "generate_all",
		"state_count", len(next.ByFieldPath),
		"state_sources", summarizeDraftSuggestionSources(next),
	)
	mappingLogger().DebugContext(ctx, "autofill state transition",
		"component", "autofill",
		"step", "post_generate_apply_state",
		"action", "generate_all",
		"sample_sources", sampleDraftSuggestionSources(next, 5),
	)
	next.UpdatedAt = now.UTC()
	return next
}

func RegenerateAllSuggestions(ctx context.Context, generator SuggestionGenerator, req SuggestionRequest, current domain.DraftSuggestionState, now time.Time) domain.DraftSuggestionState {
	next := cloneSuggestionState(current)
	next.ByFieldPath = map[string]domain.DraftSuggestion{}
	generated, err := suggestionResultWithFallback(ctx, generator, SuggestionRequest{
		TemplateID:    req.TemplateID,
		DraftID:       req.DraftID,
		Fields:        req.Fields,
		GlobalData:    req.GlobalData,
		DraftContext:  req.DraftContext,
		MasterPrompt:  req.MasterPrompt,
		PromptBlocks:  req.PromptBlocks,
		Existing:      req.Existing,
		IncludeFilled: true,
	})
	if err != nil {
		return next
	}
	mappingLogger().InfoContext(ctx, "autofill state transition",
		"component", "autofill",
		"step", "post_generate_result",
		"action", "regenerate_all",
		"generated_count", len(generated.ByFieldPath),
		"generated_sources", summarizeResultSources(generated),
	)
	mappingLogger().DebugContext(ctx, "autofill state transition",
		"component", "autofill",
		"step", "post_generate_result",
		"action", "regenerate_all",
		"sample_sources", sampleResultSources(generated, 5),
	)
	for _, s := range generated.Suggestions {
		sliceSource := strings.TrimSpace(s.Source)
		canonicalSource := ""
		if canonical, ok := generated.ByFieldPath[strings.TrimSpace(s.FieldPath)]; ok {
			canonicalSource = strings.TrimSpace(canonical.Source)
			s = canonical
		}
		stored := toDraftSuggestion(s, now)
		if explicitSource := strings.TrimSpace(s.Source); explicitSource != "" {
			stored.Source = explicitSource
		}
		mappingLogger().DebugContext(ctx, "autofill state transition",
			"component", "autofill",
			"step", "apply_field_transition",
			"action", "regenerate_all",
			"field_path", strings.TrimSpace(s.FieldPath),
			"slice_source", firstNonEmpty(sliceSource, "unknown"),
			"canonical_source", firstNonEmpty(canonicalSource, "unknown"),
			"stored_source", firstNonEmpty(strings.TrimSpace(stored.Source), "unknown"),
		)
		next.ByFieldPath[s.FieldPath] = stored
	}
	mappingLogger().InfoContext(ctx, "autofill state transition",
		"component", "autofill",
		"step", "post_generate_apply_state",
		"action", "regenerate_all",
		"state_count", len(next.ByFieldPath),
		"state_sources", summarizeDraftSuggestionSources(next),
	)
	mappingLogger().DebugContext(ctx, "autofill state transition",
		"component", "autofill",
		"step", "post_generate_apply_state",
		"action", "regenerate_all",
		"sample_sources", sampleDraftSuggestionSources(next, 5),
	)
	next.UpdatedAt = now.UTC()
	return next
}

func RegenerateFieldSuggestion(ctx context.Context, generator SuggestionGenerator, req SuggestionRequest, current domain.DraftSuggestionState, fieldPath string, now time.Time) domain.DraftSuggestionState {
	target := strings.TrimSpace(fieldPath)
	if target == "" {
		return cloneSuggestionState(current)
	}
	next := cloneSuggestionState(current)
	if next.ByFieldPath == nil {
		next.ByFieldPath = map[string]domain.DraftSuggestion{}
	}
	generated, err := suggestionResultWithFallback(ctx, generator, SuggestionRequest{
		TemplateID:    req.TemplateID,
		DraftID:       req.DraftID,
		Fields:        req.Fields,
		GlobalData:    req.GlobalData,
		DraftContext:  req.DraftContext,
		MasterPrompt:  req.MasterPrompt,
		PromptBlocks:  req.PromptBlocks,
		Existing:      req.Existing,
		IncludeFilled: true,
	})
	if err != nil {
		return next
	}
	if suggestion, ok := generated.ByFieldPath[target]; ok {
		next.ByFieldPath[target] = toDraftSuggestion(suggestion, now)
		next.UpdatedAt = now.UTC()
	}
	return next
}

func ApplySuggestionsToEmptyFields(fieldValues map[string]string, state domain.DraftSuggestionState, now time.Time) (map[string]string, domain.DraftSuggestionState) {
	values := cloneFieldValues(fieldValues)
	next := cloneSuggestionState(state)
	if next.ByFieldPath == nil {
		next.ByFieldPath = map[string]domain.DraftSuggestion{}
	}
	for path, suggestion := range next.ByFieldPath {
		if strings.TrimSpace(values[path]) != "" {
			continue
		}
		if strings.TrimSpace(suggestion.Value) == "" {
			continue
		}
		values[path] = strings.TrimSpace(suggestion.Value)
		suggestion.Status = domain.DraftSuggestionStatusApplied
		suggestion.UpdatedAt = now.UTC()
		next.ByFieldPath[path] = suggestion
	}
	next.UpdatedAt = now.UTC()
	return values, next
}

func ApplyAllSuggestions(fieldValues map[string]string, state domain.DraftSuggestionState, now time.Time) (map[string]string, domain.DraftSuggestionState) {
	values := cloneFieldValues(fieldValues)
	next := cloneSuggestionState(state)
	if next.ByFieldPath == nil {
		next.ByFieldPath = map[string]domain.DraftSuggestion{}
	}
	for path, suggestion := range next.ByFieldPath {
		if strings.TrimSpace(suggestion.Value) == "" {
			continue
		}
		values[path] = strings.TrimSpace(suggestion.Value)
		suggestion.Status = domain.DraftSuggestionStatusApplied
		suggestion.UpdatedAt = now.UTC()
		next.ByFieldPath[path] = suggestion
	}
	next.UpdatedAt = now.UTC()
	return values, next
}

func ApplySuggestionToField(fieldValues map[string]string, state domain.DraftSuggestionState, fieldPath string, now time.Time) (map[string]string, domain.DraftSuggestionState) {
	target := strings.TrimSpace(fieldPath)
	values := cloneFieldValues(fieldValues)
	next := cloneSuggestionState(state)
	if target == "" || next.ByFieldPath == nil {
		return values, next
	}
	suggestion, ok := next.ByFieldPath[target]
	if !ok || strings.TrimSpace(suggestion.Value) == "" {
		return values, next
	}
	values[target] = strings.TrimSpace(suggestion.Value)
	suggestion.Status = domain.DraftSuggestionStatusApplied
	suggestion.UpdatedAt = now.UTC()
	next.ByFieldPath[target] = suggestion
	next.UpdatedAt = now.UTC()
	return values, next
}

func cloneFieldValues(values map[string]string) map[string]string {
	if values == nil {
		return map[string]string{}
	}
	out := make(map[string]string, len(values))
	for k, v := range values {
		out[k] = v
	}
	return out
}

func cloneSuggestionState(state domain.DraftSuggestionState) domain.DraftSuggestionState {
	out := domain.DraftSuggestionState{
		ByFieldPath: map[string]domain.DraftSuggestion{},
		UpdatedAt:   state.UpdatedAt,
	}
	for path, suggestion := range state.ByFieldPath {
		out.ByFieldPath[path] = suggestion
	}
	return out
}

func toDraftSuggestion(s Suggestion, now time.Time) domain.DraftSuggestion {
	ts := now.UTC()
	source := strings.TrimSpace(s.Source)
	if source == "" {
		source = "unknown"
	}
	return domain.DraftSuggestion{
		FieldPath:   strings.TrimSpace(s.FieldPath),
		Slot:        strings.TrimSpace(s.Slot),
		Value:       strings.TrimSpace(s.Value),
		Reason:      strings.TrimSpace(s.Reason),
		Source:      source,
		Status:      domain.DraftSuggestionStatusSuggested,
		GeneratedAt: ts,
		UpdatedAt:   ts,
	}
}

func shouldReplaceExistingSuggestion(existing domain.DraftSuggestion, generated Suggestion) bool {
	existingSource := strings.TrimSpace(existing.Source)
	generatedSource := strings.TrimSpace(generated.Source)
	if generatedSource == "" {
		return false
	}
	if generatedSource == domain.DraftSuggestionSourceFallbackRuleBased {
		return false
	}
	return existingSource == domain.DraftSuggestionSourceFallbackRuleBased
}

func suggestionResultWithFallback(ctx context.Context, generator SuggestionGenerator, req SuggestionRequest) (SuggestionResult, error) {
	if generator == nil {
		return NewRuleBasedSuggestionGenerator().Generate(ctx, req)
	}
	return generator.Generate(ctx, req)
}

func summarizeResultSources(result SuggestionResult) map[string]int {
	if len(result.ByFieldPath) == 0 {
		return map[string]int{}
	}
	out := map[string]int{}
	for _, suggestion := range result.ByFieldPath {
		source := strings.TrimSpace(suggestion.Source)
		if source == "" {
			source = "unknown"
		}
		out[source]++
	}
	return out
}

func summarizeDraftSuggestionSources(state domain.DraftSuggestionState) map[string]int {
	if len(state.ByFieldPath) == 0 {
		return map[string]int{}
	}
	out := map[string]int{}
	for _, suggestion := range state.ByFieldPath {
		source := strings.TrimSpace(suggestion.Source)
		if source == "" {
			source = "unknown"
		}
		out[source]++
	}
	return out
}

func sampleResultSources(result SuggestionResult, limit int) map[string]string {
	if limit <= 0 || len(result.ByFieldPath) == 0 {
		return map[string]string{}
	}
	paths := make([]string, 0, len(result.ByFieldPath))
	for path := range result.ByFieldPath {
		paths = append(paths, path)
	}
	sort.Strings(paths)
	if len(paths) > limit {
		paths = paths[:limit]
	}
	out := make(map[string]string, len(paths))
	for _, path := range paths {
		source := strings.TrimSpace(result.ByFieldPath[path].Source)
		if source == "" {
			source = "unknown"
		}
		out[path] = source
	}
	return out
}

func sampleDraftSuggestionSources(state domain.DraftSuggestionState, limit int) map[string]string {
	if limit <= 0 || len(state.ByFieldPath) == 0 {
		return map[string]string{}
	}
	paths := make([]string, 0, len(state.ByFieldPath))
	for path := range state.ByFieldPath {
		paths = append(paths, path)
	}
	sort.Strings(paths)
	if len(paths) > limit {
		paths = paths[:limit]
	}
	out := make(map[string]string, len(paths))
	for _, path := range paths {
		source := strings.TrimSpace(state.ByFieldPath[path].Source)
		if source == "" {
			source = "unknown"
		}
		out[path] = source
	}
	return out
}
