// Copyright 2025 Emiliano Spinella (eminwux)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

// Package cellconfig holds the identity primitives for a daemon-stored
// CellConfig (kind: CellConfig, issue #624, phase 4b-i of #423): the
// back-reference label a materialized cell carries, the deterministic
// stable-name derivation, and the slot-fill validation that checks a Config's
// repo/secret fills against the referenced blueprint's declared structural
// slots. It is the config analog of the cellblueprint package's materialization
// helpers; the runtime state machine that consumes these primitives (and the
// `kuke run -c` verb that drives it) lands in #625.
package cellconfig

import (
	"fmt"
	"strings"

	"github.com/eminwux/kukeon/internal/errdefs"
	v1beta1 "github.com/eminwux/kukeon/pkg/api/model/v1beta1"
)

// LabelConfig is the cell *lineage* label recording the CellConfig a cell was
// materialized from, the config analog of cellprofile.LabelProfile and
// cellblueprint.LabelBlueprint. Set on every cell that `kuke run <config>`
// materializes (#625) so operators can enumerate all of a Config's spawns with
// `kuke get cells -l kukeon.io/config=<name>`.
//
// The relationship is 1:N, not identity (epic:cell-identity #1021): a single
// Config may stamp many cells, and the cell's identity is its own name on the
// CellDoc — not this label. The persisted Spec.Provenance block carries the
// same binding reference in a typed, machine-readable form for re-resolution;
// this label is the operator-facing, `-l`-selectable projection of it.
const LabelConfig = "kukeon.io/config"

// Prefix resolves the cell-name prefix the unified generator uses for a cell
// materialized from this Config (epic:cell-identity #1022): Spec.Prefix when
// set, else the config's metadata.name. It mirrors cellblueprint.Prefix.
//
// This is the prefix-default helper that replaces the retired StableName
// identity pin. A Config is no longer a 1:1 identity (#1020 moves identity onto
// the CellDoc): a `create cell --from-config` with no explicit name yields a
// fresh `<prefix>-<6hex>` cell, so Prefix supplies only the leading segment,
// never the whole deterministic name.
func Prefix(cfg v1beta1.CellConfigDoc) string {
	if p := strings.TrimSpace(cfg.Spec.Prefix); p != "" {
		return p
	}
	return strings.TrimSpace(cfg.Metadata.Name)
}

// ValidateSlotFill checks a CellConfig's structural slot fills against the slots
// the referenced blueprint declares (issue #624). Matching is by slot name,
// across all of the blueprint's containers:
//
//   - a repo slot is a blueprint repo with no inline url (a url'd repo is a
//     scalar-mode value, not a fillable slot);
//   - every blueprint secret slot is structural (the blueprint never carries
//     the secret source).
//
// It enforces the AC's two gates: a Config that fills a slot the blueprint does
// not declare is an error (ErrConfigUnknown{Repo,Secret}Slot), and a *required*
// slot the blueprint declares that the Config leaves unfilled is an error
// (ErrConfigRequiredSlotUnfilled). A slot is treated as required if any
// declaration of that name across the blueprint's containers is required.
func ValidateSlotFill(cfg v1beta1.CellConfigDoc, bp v1beta1.CellBlueprintDoc) error {
	repoRequired := map[string]bool{}
	secretRequired := map[string]bool{}
	for _, c := range bp.Spec.Cell.Containers {
		for _, r := range c.Repos {
			if strings.TrimSpace(r.URL) != "" {
				continue // scalar-mode repo, not a fillable slot
			}
			name := strings.TrimSpace(r.Name)
			repoRequired[name] = repoRequired[name] || r.Required
		}
		for _, s := range c.Secrets {
			name := strings.TrimSpace(s.Name)
			secretRequired[name] = secretRequired[name] || s.Required
		}
	}

	for name := range cfg.Spec.Repos {
		if _, ok := repoRequired[strings.TrimSpace(name)]; !ok {
			return fmt.Errorf("%w: repo slot %q", errdefs.ErrConfigUnknownRepoSlot, name)
		}
	}
	for name := range cfg.Spec.Secrets {
		if _, ok := secretRequired[strings.TrimSpace(name)]; !ok {
			return fmt.Errorf("%w: secret slot %q", errdefs.ErrConfigUnknownSecretSlot, name)
		}
	}

	for name, required := range repoRequired {
		if required {
			if _, ok := cfg.Spec.Repos[name]; !ok {
				return fmt.Errorf("%w: repo slot %q", errdefs.ErrConfigRequiredSlotUnfilled, name)
			}
		}
	}
	for name, required := range secretRequired {
		if required {
			if _, ok := cfg.Spec.Secrets[name]; !ok {
				return fmt.Errorf("%w: secret slot %q", errdefs.ErrConfigRequiredSlotUnfilled, name)
			}
		}
	}

	return nil
}
