package persistence

import (
	"compress/gzip"
	"crypto/sha256"
	"encoding/gob"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/zzet/gortex/internal/pathkey"
)

const (
	feedbackFile   = "feedback.gob.gz"
	maxFeedbackCap = 500
)

// FeedbackEntry records one agent feedback event about a context call.
type FeedbackEntry struct {
	Timestamp time.Time
	Task      string   // task description from the context call
	Useful    []string // symbol IDs the agent found useful
	NotNeeded []string // symbol IDs returned but not needed
	Missing   []string // symbol IDs that should have been included
	Source    string   // "smart_context" or "prefetch_context"
}

// FeedbackStore holds all feedback entries for a single repo.
// Persisted separately from the graph snapshot (repo-scoped, not commit-scoped).
type FeedbackStore struct {
	Version  string
	RepoPath string
	Entries  []FeedbackEntry
}

// RepoCacheKey produces a filesystem-safe directory name from repo path alone
// (no commit hash). Used for data that persists across commits, like feedback.
//
// The path is folded to Unicode NFC before hashing so a repo whose path
// contains non-ASCII characters keys to the same directory whether the
// caller passes a decomposed (macOS) or precomposed (Linux / git) form.
func RepoCacheKey(repoPath string) string {
	abs, err := filepath.Abs(repoPath)
	if err != nil {
		abs = repoPath
	}
	h := sha256.Sum256([]byte(pathkey.Normalize(abs)))
	return hex.EncodeToString(h[:6]) + "_latest"
}

// FeedbackDir returns the full directory path for feedback storage.
func FeedbackDir(cacheDir, repoPath string) string {
	return filepath.Join(cacheDir, RepoCacheKey(repoPath))
}

// LoadFeedback reads a feedback store from disk. Returns an empty store if
// the file does not exist (not an error — cold start is normal).
func LoadFeedback(dir string) (*FeedbackStore, error) {
	path := filepath.Join(dir, feedbackFile)
	f, err := os.Open(path)
	if err != nil {
		if os.IsNotExist(err) {
			return &FeedbackStore{}, nil
		}
		return nil, fmt.Errorf("persistence: open feedback: %w", err)
	}
	defer func() { _ = f.Close() }()

	gz, err := gzip.NewReader(f)
	if err != nil {
		return nil, fmt.Errorf("persistence: gzip reader: %w", err)
	}
	defer func() { _ = gz.Close() }()

	var store FeedbackStore
	if err := gob.NewDecoder(gz).Decode(&store); err != nil {
		return nil, fmt.Errorf("persistence: gob decode feedback: %w", err)
	}
	return &store, nil
}

// SaveFeedback writes a feedback store to disk with gob+gzip compression.
// Entries are trimmed to maxFeedbackCap before writing (oldest removed).
func SaveFeedback(dir string, store *FeedbackStore) error {
	// Trim oldest entries if over cap.
	if len(store.Entries) > maxFeedbackCap {
		store.Entries = store.Entries[len(store.Entries)-maxFeedbackCap:]
	}

	if err := os.MkdirAll(dir, 0o755); err != nil {
		return fmt.Errorf("persistence: mkdir feedback: %w", err)
	}

	path := filepath.Join(dir, feedbackFile)
	f, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("persistence: create feedback: %w", err)
	}

	gz := gzip.NewWriter(f)
	enc := gob.NewEncoder(gz)

	if err := enc.Encode(store); err != nil {
		_ = gz.Close()
		_ = f.Close()
		return fmt.Errorf("persistence: gob encode feedback: %w", err)
	}

	if err := gz.Close(); err != nil {
		_ = f.Close()
		return fmt.Errorf("persistence: gzip close feedback: %w", err)
	}

	return f.Close()
}
