package service

import (
	"encoding/json"
	"fmt"
	"path/filepath"
	"regexp"
	"strings"
)

var slotTokenRE = regexp.MustCompile(`^\$\{slot:([A-Za-z0-9_.-]+)\.([A-Za-z0-9_.-]+)\}$`)

func renderRuntimeConfig(templateRaw map[string]any, templatePath string, profileRaw map[string]any, profilePath string, overlays []runtimeOverlayInput, metadata map[string]any) (map[string]any, error) {
	tplName, err := runtimeTemplateName(templateRaw, templatePath)
	if err != nil {
		return nil, err
	}
	instances, err := runtimeProfileInstances(profileRaw, tplName)
	if err != nil {
		return nil, err
	}
	instances, err = mergeRuntimeTemplateParams(instances, runtimeTemplateParams(templateRaw))
	if err != nil {
		return nil, err
	}

	renderedTemplates := map[string]any{}
	renderedInstances := make([]any, 0, len(instances))
	for _, instance := range instances {
		boundName, boundTemplate, renderedInstance, err := renderRuntimeSceneInstance(templateRaw, templatePath, instance)
		if err != nil {
			return nil, err
		}
		renderedTemplates[boundName] = boundTemplate
		renderedInstances = append(renderedInstances, renderedInstance)
	}

	root := map[string]any{
		"templates": renderedTemplates,
		"instances": renderedInstances,
	}
	for _, key := range []string{"global", "queue"} {
		if value, ok := profileRaw[key]; ok {
			root[key] = deepCopyAny(value)
		}
	}

	for _, overlay := range overlays {
		var err error
		root, err = applyRuntimeOverlay(root, overlay.Raw)
		if err != nil {
			return nil, err
		}
	}

	if metadata != nil {
		root["metadata"] = buildRuntimeMetadata(tplName, templatePath, profileRaw, profilePath, overlays, root, metadata)
	}
	return root, nil
}

type runtimeOverlayInput struct {
	Name string
	Path string
	Raw  map[string]any
}

func runtimeTemplateName(templateRaw map[string]any, templatePath string) (string, error) {
	name := strings.TrimSpace(firstString(templateRaw["name"], strings.TrimSuffix(filepath.Base(templatePath), filepath.Ext(templatePath))))
	if name == "" {
		return "", fmt.Errorf("%s: template name is empty", templatePath)
	}
	return name, nil
}

func runtimeTemplateBody(templateRaw map[string]any) (map[string]any, error) {
	body := templateRaw
	if nested, ok := templateRaw["template"].(map[string]any); ok {
		body = nested
	}
	nodes, hasNodes := body["nodes"].([]any)
	edges, hasEdges := body["edges"].([]any)
	if !hasNodes || !hasEdges {
		return nil, fmt.Errorf("template body must contain nodes[] and edges[]")
	}
	out := map[string]any{
		"nodes": deepCopyAny(nodes),
		"edges": deepCopyAny(edges),
	}
	if executor, ok := body["executor"]; ok {
		out["executor"] = deepCopyAny(executor)
	}
	return out, nil
}

func runtimeTemplateParams(templateRaw map[string]any) map[string]any {
	params, _ := templateRaw["params"].(map[string]any)
	if params == nil {
		return map[string]any{}
	}
	return cloneMap(params)
}

func runtimeProfileInstances(profileRaw map[string]any, tplName string) ([]map[string]any, error) {
	if items, ok := profileRaw["instances"].([]any); ok {
		out := make([]map[string]any, 0, len(items))
		for _, item := range items {
			instance, _ := item.(map[string]any)
			if instance == nil {
				return nil, fmt.Errorf("profile.instances entries must be objects")
			}
			cloned := deepCopyMap(instance)
			if strings.TrimSpace(stringValue(cloned["template"])) == "" {
				cloned["template"] = tplName
			}
			out = append(out, cloned)
		}
		return out, nil
	}
	name := strings.TrimSpace(stringValue(profileRaw["name"]))
	if name == "" {
		return nil, fmt.Errorf("profile must contain name or instances[]")
	}
	instance := map[string]any{
		"name":     name,
		"template": tplName,
	}
	if params, ok := profileRaw["params"].(map[string]any); ok && len(params) > 0 {
		instance["params"] = deepCopyMap(params)
	}
	if override, ok := profileRaw["override"].(map[string]any); ok && len(override) > 0 {
		instance["override"] = deepCopyMap(override)
	}
	return []map[string]any{instance}, nil
}

func mergeRuntimeTemplateParams(instances []map[string]any, sharedParams map[string]any) ([]map[string]any, error) {
	if len(sharedParams) == 0 {
		return instances, nil
	}
	out := make([]map[string]any, 0, len(instances))
	for _, item := range instances {
		inst := deepCopyMap(item)
		params, _ := inst["params"].(map[string]any)
		if params == nil {
			params = map[string]any{}
		}
		inst["params"] = deepMergeMap(sharedParams, params)
		out = append(out, inst)
	}
	return out, nil
}

func renderRuntimeSceneInstance(templateRaw map[string]any, templatePath string, instance map[string]any) (string, map[string]any, map[string]any, error) {
	instanceName := strings.TrimSpace(stringValue(instance["name"]))
	if instanceName == "" {
		return "", nil, nil, fmt.Errorf("scene instance name is required")
	}
	tplName, err := runtimeTemplateName(templateRaw, templatePath)
	if err != nil {
		return "", nil, nil, err
	}
	boundName := tplName + "__" + instanceName
	context, err := buildRuntimeBindingContext(instance)
	if err != nil {
		return "", nil, nil, err
	}
	templateBody, err := runtimeTemplateBody(templateRaw)
	if err != nil {
		return "", nil, nil, err
	}
	slotRequirements, err := runtimeSlotRequirements(templateRaw)
	if err != nil {
		return "", nil, nil, err
	}
	renderedTemplateAny, err := expandRuntimeSlotTokens(templateBody, context, slotRequirements)
	if err != nil {
		return "", nil, nil, err
	}
	renderedTemplate, _ := renderedTemplateAny.(map[string]any)
	renderedInstance := map[string]any{
		"name":     instanceName,
		"template": boundName,
	}
	if sceneMeta, ok := instance["scene_meta"].(map[string]any); ok && len(sceneMeta) > 0 {
		renderedInstance["scene_meta"] = deepCopyMap(sceneMeta)
	}
	if params, ok := instance["params"].(map[string]any); ok && len(params) > 0 {
		renderedInstance["params"] = deepCopyMap(params)
	}
	return boundName, renderedTemplate, renderedInstance, nil
}

func runtimeSlotRequirements(templateRaw map[string]any) (map[string]bool, error) {
	group, err := parseTemplateSlots(templateRaw)
	if err != nil {
		return nil, err
	}
	out := map[string]bool{}
	for _, slot := range group.Inputs {
		out[slot.Name] = slot.Required
	}
	for _, slot := range group.Services {
		out[slot.Name] = slot.Required
	}
	for _, slot := range group.Outputs {
		out[slot.Name] = slot.Required
	}
	return out, nil
}

func buildRuntimeBindingContext(instance map[string]any) (map[string]any, error) {
	context := map[string]any{}
	if sceneMeta, ok := instance["scene_meta"].(map[string]any); ok && len(sceneMeta) > 0 {
		context["scene"] = deepCopyMap(sceneMeta)
	}
	for _, groupName := range []string{"input_bindings", "service_bindings", "output_bindings"} {
		group, _ := instance[groupName].(map[string]any)
		for slotName, raw := range group {
			entry, _ := raw.(map[string]any)
			if entry == nil {
				return nil, fmt.Errorf("binding entry must be an object")
			}
			context[slotName] = resolvedRuntimeBindingValue(entry)
		}
	}
	return context, nil
}

func resolvedRuntimeBindingValue(entry map[string]any) map[string]any {
	if resolved, ok := entry["resolved"].(map[string]any); ok && resolved != nil {
		return deepCopyMap(resolved)
	}
	return deepCopyMap(entry)
}

func expandRuntimeSlotTokens(value any, context map[string]any, slotRequirements map[string]bool) (any, error) {
	switch typed := value.(type) {
	case map[string]any:
		out := make(map[string]any, len(typed))
		for key, item := range typed {
			expanded, err := expandRuntimeSlotTokens(item, context, slotRequirements)
			if err != nil {
				return nil, err
			}
			out[key] = expanded
		}
		return out, nil
	case []any:
		out := make([]any, 0, len(typed))
		for _, item := range typed {
			expanded, err := expandRuntimeSlotTokens(item, context, slotRequirements)
			if err != nil {
				return nil, err
			}
			out = append(out, expanded)
		}
		return out, nil
	case string:
		match := slotTokenRE.FindStringSubmatch(strings.TrimSpace(typed))
		if len(match) != 3 {
			return typed, nil
		}
		required, known := slotRequirements[match[1]]
		if !known {
			required = true
		}
		slotValues, _ := context[match[1]].(map[string]any)
		if slotValues == nil {
			if !required {
				return "", nil
			}
			return nil, fmt.Errorf("required slot '%s' is not bound", match[1])
		}
		fieldValue, ok := slotValues[match[2]]
		if !ok {
			if !required {
				return "", nil
			}
			return nil, fmt.Errorf("required slot field '%s.%s' is not bound", match[1], match[2])
		}
		return deepCopyAny(fieldValue), nil
	default:
		return deepCopyAny(value), nil
	}
}

func applyRuntimeOverlay(root map[string]any, overlay map[string]any) (map[string]any, error) {
	out := deepCopyMap(root)
	for _, key := range []string{"global", "queue", "templates"} {
		if value, ok := overlay[key]; ok {
			out[key] = deepMergeAny(out[key], value)
		}
	}
	if rawPatches, ok := overlay["instance_overrides"]; ok {
		patches, _ := rawPatches.(map[string]any)
		if patches == nil {
			return nil, fmt.Errorf("overlay.instance_overrides must be an object")
		}
		instances, _ := out["instances"].([]any)
		mergedInstances := make([]any, 0, len(instances))
		for _, item := range instances {
			instance, _ := item.(map[string]any)
			merged := deepCopyMap(instance)
			if patch, ok := patches["*"].(map[string]any); ok {
				merged = mergeRuntimeInstancePatch(merged, patch)
			}
			name := stringValue(merged["name"])
			if patch, ok := patches[name].(map[string]any); ok {
				merged = mergeRuntimeInstancePatch(merged, patch)
			}
			mergedInstances = append(mergedInstances, merged)
		}
		out["instances"] = mergedInstances
	}
	if rawInstances, ok := overlay["instances"]; ok {
		patchList, _ := rawInstances.([]any)
		if patchList == nil {
			return nil, fmt.Errorf("overlay.instances must be an array")
		}
		instances, _ := out["instances"].([]any)
		byName := map[string]int{}
		for i, item := range instances {
			instance, _ := item.(map[string]any)
			byName[stringValue(instance["name"])] = i
		}
		for _, item := range patchList {
			patch, _ := item.(map[string]any)
			name := stringValue(patch["name"])
			if patch == nil || name == "" {
				return nil, fmt.Errorf("overlay.instances entries must be objects with name")
			}
			idx, ok := byName[name]
			if !ok {
				return nil, fmt.Errorf("overlay instance not found in profile: %s", name)
			}
			instance, _ := instances[idx].(map[string]any)
			instances[idx] = mergeRuntimeInstancePatch(instance, patch)
		}
		out["instances"] = instances
	}
	return out, nil
}

func mergeRuntimeInstancePatch(instance map[string]any, patch map[string]any) map[string]any {
	merged := deepCopyMap(instance)
	if params, ok := patch["params"].(map[string]any); ok {
		existing, _ := merged["params"].(map[string]any)
		merged["params"] = deepMergeMap(existing, params)
	}
	if override, ok := patch["override"].(map[string]any); ok {
		existing, _ := merged["override"].(map[string]any)
		merged["override"] = deepMergeMap(existing, override)
	}
	for key, value := range patch {
		if key == "name" || key == "template" || key == "params" || key == "override" {
			continue
		}
		merged[key] = deepMergeAny(merged[key], value)
	}
	return merged
}

func buildRuntimeMetadata(templateName string, templatePath string, profileRaw map[string]any, profilePath string, overlays []runtimeOverlayInput, root map[string]any, metadata map[string]any) map[string]any {
	instanceNames := make([]string, 0)
	instanceDisplayNames := make([]string, 0)
	instances, _ := root["instances"].([]any)
	for _, item := range instances {
		instance, _ := item.(map[string]any)
		if name := strings.TrimSpace(stringValue(instance["name"])); name != "" {
			instanceNames = append(instanceNames, name)
		}
		if sceneMeta, ok := instance["scene_meta"].(map[string]any); ok {
			if displayName := strings.TrimSpace(stringValue(sceneMeta["display_name"])); displayName != "" {
				instanceDisplayNames = append(instanceDisplayNames, displayName)
			}
		}
	}
	overlayNames := make([]any, 0, len(overlays))
	overlayPaths := make([]any, 0, len(overlays))
	for _, overlay := range overlays {
		overlayNames = append(overlayNames, overlay.Name)
		overlayPaths = append(overlayPaths, overlay.Path)
	}
	out := map[string]any{
		"template":               templateName,
		"template_path":          templatePath,
		"profile":                strings.TrimSpace(firstString(profileRaw["name"], strings.TrimSuffix(filepath.Base(profilePath), filepath.Ext(profilePath)))),
		"business_name":          strings.TrimSpace(stringValue(profileRaw["business_name"])),
		"profile_path":           profilePath,
		"instance_names":         instanceNames,
		"instance_display_names": instanceDisplayNames,
		"overlays":               overlayNames,
		"overlay_paths":          overlayPaths,
	}
	for key, value := range metadata {
		out[key] = deepCopyAny(value)
	}
	return out
}

func deepMergeMap(base map[string]any, override map[string]any) map[string]any {
	if base == nil {
		base = map[string]any{}
	}
	return deepMergeAny(base, override).(map[string]any)
}

func deepMergeAny(base any, override any) any {
	baseMap, baseIsMap := base.(map[string]any)
	overrideMap, overrideIsMap := override.(map[string]any)
	if baseIsMap && overrideIsMap {
		merged := deepCopyMap(baseMap)
		for key, value := range overrideMap {
			if existing, ok := merged[key]; ok {
				merged[key] = deepMergeAny(existing, value)
			} else {
				merged[key] = deepCopyAny(value)
			}
		}
		return merged
	}
	return deepCopyAny(override)
}

func deepCopyMap(in map[string]any) map[string]any {
	if in == nil {
		return map[string]any{}
	}
	out, _ := deepCopyAny(in).(map[string]any)
	return out
}

func deepCopyAny(value any) any {
	if value == nil {
		return nil
	}
	body, err := json.Marshal(value)
	if err != nil {
		return value
	}
	var out any
	if err := json.Unmarshal(body, &out); err != nil {
		return value
	}
	return out
}
