package runtime

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

	"galaxy/util"
)

// EngineVersionService implements the engine-version registry CRUD
// surface consumed by the admin endpoints under
// `/api/v1/admin/engine-versions/*`. Mutations are write-through: a
// successful Postgres write is followed by a cache update so warm
// reads observe the new state immediately.
type EngineVersionService struct {
	store *Store
	cache *Cache
	now   func() time.Time
}

// NewEngineVersionService constructs the service. now defaults to
// time.Now when nil.
func NewEngineVersionService(store *Store, cache *Cache, now func() time.Time) *EngineVersionService {
	if now == nil {
		now = time.Now
	}
	return &EngineVersionService{store: store, cache: cache, now: now}
}

// List returns every engine_versions row ordered by created_at DESC.
// Cache-first when warm; falls back to a Postgres read otherwise.
func (s *EngineVersionService) List(ctx context.Context) ([]EngineVersion, error) {
	if s.cache != nil && s.cache.Ready() {
		out := s.cache.ListEngineVersions()
		sort.SliceStable(out, func(i, j int) bool {
			if !out[i].CreatedAt.Equal(out[j].CreatedAt) {
				return out[i].CreatedAt.After(out[j].CreatedAt)
			}
			return out[i].Version > out[j].Version
		})
		return out, nil
	}
	return s.store.ListEngineVersions(ctx)
}

// Get returns the row for version. Returns ErrNotFound on miss.
func (s *EngineVersionService) Get(ctx context.Context, version string) (EngineVersion, error) {
	version = strings.TrimSpace(version)
	if version == "" {
		return EngineVersion{}, fmt.Errorf("%w: version must not be empty", ErrInvalidInput)
	}
	if s.cache != nil {
		if v, ok := s.cache.GetEngineVersion(version); ok {
			return v, nil
		}
	}
	v, err := s.store.GetEngineVersion(ctx, version)
	if err != nil {
		return EngineVersion{}, err
	}
	if s.cache != nil {
		s.cache.PutEngineVersion(v)
	}
	return v, nil
}

// RegisterInput is the parameter struct for Register.
type RegisterInput struct {
	Version  string
	ImageRef string
	Enabled  *bool
}

// Validate normalises the request and rejects empty / malformed
// fields. Semver is enforced via `pkg/util.ParseSemver`.
func (in *RegisterInput) Validate() error {
	in.Version = strings.TrimSpace(in.Version)
	in.ImageRef = strings.TrimSpace(in.ImageRef)
	if in.Version == "" {
		return fmt.Errorf("%w: version must not be empty", ErrInvalidInput)
	}
	if _, err := util.ParseSemver(in.Version); err != nil {
		return fmt.Errorf("%w: version %q is not a valid semver: %v", ErrInvalidInput, in.Version, err)
	}
	if in.ImageRef == "" {
		return fmt.Errorf("%w: image_ref must not be empty", ErrInvalidInput)
	}
	return nil
}

// Register persists a fresh engine_versions row. Returns
// ErrEngineVersionTaken on duplicate version.
func (s *EngineVersionService) Register(ctx context.Context, in RegisterInput) (EngineVersion, error) {
	if err := (&in).Validate(); err != nil {
		return EngineVersion{}, err
	}
	enabled := true
	if in.Enabled != nil {
		enabled = *in.Enabled
	}
	now := s.now().UTC()
	v, err := s.store.InsertEngineVersion(ctx, in.Version, in.ImageRef, enabled, now)
	if err != nil {
		return EngineVersion{}, err
	}
	if s.cache != nil {
		s.cache.PutEngineVersion(v)
	}
	return v, nil
}

// UpdateInput is the parameter struct for Update. Nil pointers leave
// the corresponding column alone.
type UpdateInput struct {
	ImageRef *string
	Enabled  *bool
}

// Update patches mutable fields on an existing row.
func (s *EngineVersionService) Update(ctx context.Context, version string, in UpdateInput) (EngineVersion, error) {
	version = strings.TrimSpace(version)
	if version == "" {
		return EngineVersion{}, fmt.Errorf("%w: version must not be empty", ErrInvalidInput)
	}
	patch := engineVersionUpdate{Enabled: in.Enabled}
	if in.ImageRef != nil {
		trimmed := strings.TrimSpace(*in.ImageRef)
		if trimmed == "" {
			return EngineVersion{}, fmt.Errorf("%w: image_ref must not be empty", ErrInvalidInput)
		}
		patch.ImageRef = &trimmed
	}
	now := s.now().UTC()
	v, err := s.store.UpdateEngineVersion(ctx, version, patch, now)
	if err != nil {
		return EngineVersion{}, err
	}
	if s.cache != nil {
		s.cache.PutEngineVersion(v)
	}
	return v, nil
}

// Disable flips the enabled flag to false. Idempotent.
func (s *EngineVersionService) Disable(ctx context.Context, version string) (EngineVersion, error) {
	disabled := false
	return s.Update(ctx, version, UpdateInput{Enabled: &disabled})
}

// Resolve returns the row for version, rejecting disabled rows with
// ErrEngineVersionDisabled. Used by `Service.StartGame` /
// `AdminPatch` / `AdminRestart` before the docker pull.
func (s *EngineVersionService) Resolve(ctx context.Context, version string) (EngineVersion, error) {
	v, err := s.Get(ctx, version)
	if err != nil {
		return EngineVersion{}, err
	}
	if !v.Enabled {
		return EngineVersion{}, fmt.Errorf("%w: %s", ErrEngineVersionDisabled, v.Version)
	}
	return v, nil
}

// CheckPatchCompatible verifies the requested target version stays
// inside the same major+minor line as `currentVersion`. Returns
// ErrPatchSemverIncompatible otherwise.
func CheckPatchCompatible(currentVersion, targetVersion string) error {
	current, err := util.ParseSemver(currentVersion)
	if err != nil {
		return fmt.Errorf("%w: current version %q: %v", ErrInvalidInput, currentVersion, err)
	}
	target, err := util.ParseSemver(targetVersion)
	if err != nil {
		return fmt.Errorf("%w: target version %q: %v", ErrInvalidInput, targetVersion, err)
	}
	if current.Major != target.Major || current.Minor != target.Minor {
		return fmt.Errorf("%w: %s -> %s", ErrPatchSemverIncompatible, currentVersion, targetVersion)
	}
	return nil
}

// IsKnownEngineVersion is a small helper used by tests and handlers.
func IsKnownEngineVersion(err error) bool {
	return errors.Is(err, ErrEngineVersionDisabled) || errors.Is(err, ErrPatchSemverIncompatible)
}
