// Package blob stores automation log and artifact payload objects outside
// MeshX's hot automation ledger tables.
package blob

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"path"
	"strconv"
	"strings"
	"time"
)

var (
	// ErrNotFound means the requested blob object is missing from the backing store.
	ErrNotFound = errors.New("automation blob not found")
	// ErrAlreadyExists means the target blob key exists with different content.
	ErrAlreadyExists = errors.New("automation blob already exists")
)

// Store reads and writes automation blob payloads by deterministic object key.
// P0 keeps logs and artifacts behind this small interface so the hot automation
// DB stores only metadata and references; drive9
// (https://github.com/mem9-ai/drive9) is the intended later backend direction.
type Store interface {
	Put(ctx context.Context, key string, r io.Reader, size int64) error
	Get(ctx context.Context, key string) (io.ReadCloser, error)
	Delete(ctx context.Context, key string) error
}

// Object describes one blob object for bounded maintenance scans.
type Object struct {
	Key       string
	SizeBytes int64
	ModTime   time.Time
}

// Lister is implemented by blob stores that support bounded object scans.
type Lister interface {
	List(ctx context.Context, prefix string, limit int) ([]Object, error)
}

// SHA256Hex returns the lowercase SHA-256 checksum for a automation blob payload.
func SHA256Hex(payload []byte) string {
	sum := sha256.Sum256(payload)
	return hex.EncodeToString(sum[:])
}

// ShortHash returns a deterministic SHA-256 hex prefix.
func ShortHash(value string, chars int) string {
	encoded := SHA256Hex([]byte(value))
	if chars <= 0 || chars > len(encoded) {
		return encoded
	}
	return encoded[:chars]
}

// LogChunkID returns the deterministic metadata id for a log chunk.
func LogChunkID(jobRunID string, stream string, sequence int64) string {
	return "wflc_" + ShortHash(jobRunID+"|"+stream+"|"+strconv.FormatInt(sequence, 10), 24)
}

// LogEventID returns the deterministic event id for a log chunk append event.
func LogEventID(jobRunID string, stream string, sequence int64) string {
	return "wfe_job_log_" + ShortHash(jobRunID+"|"+stream+"|"+strconv.FormatInt(sequence, 10), 24)
}

// ArtifactEventID returns the deterministic event id for an artifact append event.
func ArtifactEventID(artifactID string) string {
	return "wfe_job_artifact_" + ShortHash(artifactID, 24)
}

// LogChunkObjectKey returns the object key for one automation log chunk.
func LogChunkObjectKey(workspaceID string, runID string, jobRunID string, stream string, sequence int64) (string, error) {
	if sequence <= 0 {
		return "", fmt.Errorf("automation log sequence is required")
	}
	parts, err := scopedKeyParts(workspaceID, runID, jobRunID)
	if err != nil {
		return "", err
	}
	parts = append(parts, "logs", keySegment(stream), strconv.FormatInt(sequence, 10)+".log")
	return strings.Join(parts, "/"), nil
}

// ArtifactObjectKey returns the object key for one automation artifact payload.
func ArtifactObjectKey(workspaceID string, runID string, jobRunID string, artifactID string, name string) (string, error) {
	parts, err := scopedKeyParts(workspaceID, runID, jobRunID)
	if err != nil {
		return "", err
	}
	parts = append(parts, "artifacts", keySegment(artifactID), keySegment(name))
	return strings.Join(parts, "/"), nil
}

func scopedKeyParts(workspaceID string, runID string, jobRunID string) ([]string, error) {
	workspaceID = strings.TrimSpace(workspaceID)
	runID = strings.TrimSpace(runID)
	jobRunID = strings.TrimSpace(jobRunID)
	if workspaceID == "" || runID == "" || jobRunID == "" {
		return nil, fmt.Errorf("automation blob scope is incomplete")
	}
	return []string{"workspaces", keySegment(workspaceID), "runs", keySegment(runID), "jobs", keySegment(jobRunID)}, nil
}

func keySegment(value string) string {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "_"
	}
	for _, r := range trimmed {
		if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '_' || r == '.' {
			continue
		}
		return "h-" + ShortHash(trimmed, 32)
	}
	return trimmed
}

func cleanKey(key string) (string, error) {
	raw := strings.TrimSpace(key)
	for _, segment := range strings.Split(raw, "/") {
		if segment == "." || segment == ".." {
			return "", fmt.Errorf("automation blob key contains invalid path segment")
		}
	}
	cleaned := path.Clean(raw)
	if cleaned == "." || cleaned == "/" || strings.HasPrefix(cleaned, "/") {
		return "", fmt.Errorf("automation blob key is required")
	}
	if strings.HasPrefix(cleaned, "../") || cleaned == ".." {
		return "", fmt.Errorf("automation blob key must not escape root")
	}
	for _, segment := range strings.Split(cleaned, "/") {
		if segment == "" || segment == "." || segment == ".." {
			return "", fmt.Errorf("automation blob key contains invalid path segment")
		}
	}
	return cleaned, nil
}
