// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package bootstrap

import (
	"fmt"
	"text/template"

	"github.com/absmach/magistrala/pkg/errors"
)

var errBindingSlot = errors.New("invalid binding slot")

func validateProfileBindingSlots(profile Profile) error {
	seen := make(map[string]struct{}, len(profile.BindingSlots))
	for _, slot := range profile.BindingSlots {
		if slot.Name == "" {
			return fmt.Errorf("%w: slot name is required", errBindingSlot)
		}
		if slot.Type == "" {
			return fmt.Errorf("%w: slot %q type is required", errBindingSlot, slot.Name)
		}
		if _, ok := seen[slot.Name]; ok {
			return fmt.Errorf("%w: duplicate slot %q", errBindingSlot, slot.Name)
		}
		seen[slot.Name] = struct{}{}
	}
	return nil
}

func validateRequestedBindings(profile Profile, requested []BindingRequest) error {
	if len(profile.BindingSlots) == 0 {
		return nil
	}

	slots := make(map[string]BindingSlot, len(profile.BindingSlots))
	for _, slot := range profile.BindingSlots {
		slots[slot.Name] = slot
	}

	seen := make(map[string]struct{}, len(requested))
	for _, binding := range requested {
		slot, ok := slots[binding.Slot]
		if !ok {
			return fmt.Errorf("%w: unknown slot %q", errBindingSlot, binding.Slot)
		}
		if slot.Type != binding.Type {
			return fmt.Errorf("%w: slot %q expects %q, got %q", errBindingSlot, binding.Slot, slot.Type, binding.Type)
		}
		if _, ok := seen[binding.Slot]; ok {
			return fmt.Errorf("%w: duplicate binding for slot %q", errBindingSlot, binding.Slot)
		}
		seen[binding.Slot] = struct{}{}
	}
	return nil
}

func validateRequiredBindings(profile Profile, bindings []BindingSnapshot) error {
	if len(profile.BindingSlots) == 0 {
		return nil
	}

	bound := make(map[string]BindingSnapshot, len(bindings))
	for _, binding := range bindings {
		bound[binding.Slot] = binding
	}

	for _, slot := range profile.BindingSlots {
		binding, ok := bound[slot.Name]
		if !slot.Required && !ok {
			continue
		}
		if slot.Required && !ok {
			return fmt.Errorf("%w: required slot %q is not bound", errBindingSlot, slot.Name)
		}
		if binding.Type != slot.Type {
			return fmt.Errorf("%w: slot %q expects %q, got %q", errBindingSlot, slot.Name, slot.Type, binding.Type)
		}
	}
	return nil
}

func mergeBindingSnapshots(existing, updated []BindingSnapshot) []BindingSnapshot {
	merged := make(map[string]BindingSnapshot, len(existing)+len(updated))
	for _, binding := range existing {
		merged[binding.Slot] = binding
	}
	for _, binding := range updated {
		merged[binding.Slot] = binding
	}

	bindings := make([]BindingSnapshot, 0, len(merged))
	for _, binding := range merged {
		bindings = append(bindings, binding)
	}
	return bindings
}

func validateProfileTemplate(p Profile) error {
	if p.ContentTemplate == "" || p.ContentFormat == ContentFormatRaw {
		return nil
	}
	_, err := template.New("bootstrap").Funcs(allowlistedFuncs()).Parse(p.ContentTemplate)
	if err != nil {
		return errors.Wrap(ErrRenderFailed, err)
	}
	return nil
}
