package controllogic

import (
	"encoding/json"
	"fmt"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
)

// VersionMeta describes a single persisted revision of a control-logic graph,
// mirroring storage.VersionMeta so the frontend can treat all versioned
// document types uniformly.
type VersionMeta struct {
	Version int       `json:"version"`
	Name    string    `json:"name"`
	Tag     string    `json:"tag,omitempty"`
	Current bool      `json:"current"`
	SavedAt time.Time `json:"savedAt"`
}

// Versions returns metadata for every persisted revision of the graph, newest
// first. The live revision (held in controllogic.json) is flagged Current.
func (s *Store) Versions(id string) ([]VersionMeta, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	cur, ok := s.items[id]
	if !ok {
		return nil, ErrNotFound
	}
	curV := cur.Version
	if curV < 1 {
		curV = 1
	}

	var savedAt time.Time
	if info, err := os.Stat(s.path); err == nil {
		savedAt = info.ModTime()
	}
	out := []VersionMeta{{Version: curV, Name: cur.Name, Tag: cur.Tag, Current: true, SavedAt: savedAt}}

	entries, err := os.ReadDir(s.versionsDir)
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}
	prefix := id + ".v"
	for _, e := range entries {
		name := e.Name()
		if e.IsDir() || !strings.HasPrefix(name, prefix) || !strings.HasSuffix(name, ".json") {
			continue
		}
		vStr := strings.TrimSuffix(strings.TrimPrefix(name, prefix), ".json")
		v, err := strconv.Atoi(vStr)
		if err != nil {
			continue
		}
		g, err := s.readVersion(id, v)
		if err != nil {
			continue
		}
		info, err := e.Info()
		if err != nil {
			continue
		}
		out = append(out, VersionMeta{Version: v, Name: g.Name, Tag: g.Tag, SavedAt: info.ModTime()})
	}

	sort.Slice(out, func(i, j int) bool { return out[i].Version > out[j].Version })
	return out, nil
}

// GetVersion returns a specific revision of the graph. The current revision is
// served from the live store; older revisions come from their backup file.
func (s *Store) GetVersion(id string, version int) (Graph, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	cur, ok := s.items[id]
	if !ok {
		return Graph{}, ErrNotFound
	}
	curV := cur.Version
	if curV < 1 {
		curV = 1
	}
	if version == curV {
		return cur, nil
	}
	return s.readVersion(id, version)
}

// readVersion loads a backup revision file. Caller must hold s.mu.
func (s *Store) readVersion(id string, version int) (Graph, error) {
	data, err := os.ReadFile(s.versionPath(id, version))
	if os.IsNotExist(err) {
		return Graph{}, ErrNotFound
	}
	if err != nil {
		return Graph{}, err
	}
	var g Graph
	if err := json.Unmarshal(data, &g); err != nil {
		return Graph{}, fmt.Errorf("parse revision: %w", err)
	}
	return g, nil
}

// Promote makes a past revision current by re-saving it on top of history. The
// existing current revision is preserved as a backup, so promotion is
// non-destructive. Returns the resulting (new current) graph.
func (s *Store) Promote(id string, version int) (Graph, error) {
	g, err := s.GetVersion(id, version)
	if err != nil {
		return Graph{}, err
	}
	g.Tag = fmt.Sprintf("restored from v%d", version)
	if err := s.Save(g); err != nil {
		return Graph{}, err
	}
	return s.Get(id)
}

// Fork creates a brand-new graph from a specific revision, assigning a fresh id
// and resetting its version to 1. Returns the new graph.
func (s *Store) Fork(id string, version int) (Graph, error) {
	g, err := s.GetVersion(id, version)
	if err != nil {
		return Graph{}, err
	}
	g.ID = fmt.Sprintf("%s-fork-%d", id, time.Now().UnixMilli())
	g.Version = 1
	g.Tag = ""
	if g.Name != "" {
		g.Name = g.Name + " (fork)"
	}
	if err := s.Save(g); err != nil {
		return Graph{}, err
	}
	return g, nil
}
