package semanticreconcile

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
)

// Policy identifies the framework-owned reconciliation and assessment rules.
const Policy = "semantic_reconciliation.v3"

var _ contracts.ManifestMetadataProvider = (*Engine)(nil)
var _ pipeline.CheckpointFingerprintProvider = (*Engine)(nil)

// ManifestMetadata returns fresh, content-free identity for the complete core
// reconciliation mechanism.
func (engine *Engine) ManifestMetadata() map[string]any {
	if engine == nil || engine.validate() != nil {
		return nil
	}
	return map[string]any{
		"prompt_id":                      engine.prompt.ID,
		"prompt_version":                 engine.prompt.Version,
		"prompt_sha256":                  engine.prompt.SHA256,
		"response_schema_key":            string(engine.schema.Key),
		"response_schema_id":             engine.schema.ID,
		"response_schema_name":           engine.schema.Name,
		"response_schema_version":        engine.schema.Version,
		"response_schema_sha256":         engine.schema.SHA256,
		"semantic_reconciliation_policy": Policy,
		"semantic_reconciliation_limits": map[string]any{
			"context_radius":         engine.limits.ContextRadius,
			"maximum_candidates":     engine.limits.MaximumCandidates,
			"maximum_material_bytes": engine.limits.MaximumMaterialBytes,
		},
	}
}

// CheckpointFingerprints returns fresh canonical identities for prompt,
// schema, reconciliation policy, and the complete limit policy.
func (engine *Engine) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
	if engine == nil || engine.validate() != nil {
		return nil
	}
	return []pipeline.CheckpointFingerprint{
		{Name: "prompt", Value: identityDigest(engine.prompt.ID, engine.prompt.Version, engine.prompt.SHA256)},
		{Name: "response_schema", Value: identityDigest(string(engine.schema.Key), engine.schema.ID, engine.schema.Version, engine.schema.Name, engine.schema.SHA256)},
		{Name: "semantic_reconciliation_policy", Value: Policy},
		{Name: "semantic_reconciliation_limits", Value: limitPolicyDigest(engine.limits)},
	}
}

func limitPolicyDigest(limits Limits) string {
	return identityDigest(
		strconv.Itoa(limits.ContextRadius),
		strconv.Itoa(limits.MaximumCandidates),
		strconv.Itoa(limits.MaximumMaterialBytes),
	)
}

func identityDigest(parts ...string) string {
	hash := sha256.New()
	for _, part := range parts {
		_, _ = hash.Write([]byte(strconv.Itoa(len(part))))
		_, _ = hash.Write([]byte{':'})
		_, _ = hash.Write([]byte(part))
	}
	return "sha256:" + hex.EncodeToString(hash.Sum(nil))
}

func validateResponseSchemaIdentity(schema llm.ResponseSchema) error {
	if strings.TrimSpace(string(schema.Key)) == "" || strings.TrimSpace(schema.ID) == "" || strings.TrimSpace(schema.Version) == "" || strings.TrimSpace(schema.Name) == "" {
		return fmt.Errorf("response schema identity must be complete")
	}
	if err := validateSHA256(schema.SHA256); err != nil {
		return fmt.Errorf("response schema digest: %w", err)
	}
	return nil
}

func validateSHA256(value string) error {
	const prefix = "sha256:"
	if !strings.HasPrefix(value, prefix) {
		return fmt.Errorf("must use sha256: prefix")
	}
	hexValue := strings.TrimPrefix(value, prefix)
	if len(hexValue) != sha256.Size*2 || hexValue != strings.ToLower(hexValue) {
		return fmt.Errorf("must contain 64 lowercase hexadecimal characters")
	}
	decoded, err := hex.DecodeString(hexValue)
	if err != nil || len(decoded) != sha256.Size {
		return fmt.Errorf("must contain 64 lowercase hexadecimal characters")
	}
	return nil
}
