// Package operator implements the Kubernetes operator pattern in its
// smallest correct form: an in-memory "API server" (objects with spec,
// status, generation, finalizers, deletionTimestamp), a level-triggered
// reconcile loop, finalizer-based cleanup, and status reporting. It is
// stdlib-only; production uses controller-runtime + a real apiserver, but
// the *semantics* (level-triggered, idempotent, finalizers, status) are
// identical.
package operator

import "sync"

// EdgeRouteSpec is the DESIRED state a user writes (the CR spec).
type EdgeRouteSpec struct {
	Hostname string
	Prefix   string
	Service  string
}

// Condition is one status condition (the CR status the controller writes).
type Condition struct {
	Status bool
	Reason string
}

type EdgeRouteStatus struct {
	Conditions map[string]Condition
}

// EdgeRoute is a custom resource: spec (user) + status (controller) +
// metadata (generation, finalizers, deletion).
type EdgeRoute struct {
	Name              string
	Spec              EdgeRouteSpec
	Status            EdgeRouteStatus
	Generation        int64
	Finalizers        []string
	DeletionTimestamp bool // set by Delete; finalizers block actual removal
}

func (e *EdgeRoute) hasFinalizer(f string) bool {
	for _, x := range e.Finalizers {
		if x == f {
			return true
		}
	}
	return false
}

func (e *EdgeRoute) removeFinalizer(f string) {
	out := e.Finalizers[:0]
	for _, x := range e.Finalizers {
		if x != f {
			out = append(out, x)
		}
	}
	e.Finalizers = out
}

// Store is the in-memory apiserver: EdgeRoutes + a set of existing
// Services (the "actual state" the controller observes).
type Store struct {
	mu       sync.Mutex
	routes   map[string]*EdgeRoute
	services map[string]bool
}

func NewStore() *Store {
	return &Store{routes: map[string]*EdgeRoute{}, services: map[string]bool{}}
}

// Apply creates or updates an EdgeRoute (bumping generation on spec
// change), like `kubectl apply`.
func (s *Store) Apply(name string, spec EdgeRouteSpec) {
	s.mu.Lock()
	defer s.mu.Unlock()
	o, ok := s.routes[name]
	if !ok {
		s.routes[name] = &EdgeRoute{
			Name: name, Spec: spec, Generation: 1,
			Status: EdgeRouteStatus{Conditions: map[string]Condition{}},
		}
		return
	}
	if o.Spec != spec {
		o.Spec = spec
		o.Generation++
	}
}

// Get returns a COPY of the EdgeRoute (callers must Update to persist).
func (s *Store) Get(name string) (*EdgeRoute, bool) {
	s.mu.Lock()
	defer s.mu.Unlock()
	o, ok := s.routes[name]
	if !ok {
		return nil, false
	}
	cp := *o
	cp.Finalizers = append([]string(nil), o.Finalizers...)
	cp.Status.Conditions = map[string]Condition{}
	for k, v := range o.Status.Conditions {
		cp.Status.Conditions[k] = v
	}
	return &cp, true
}

// Update persists a modified EdgeRoute. If it is being deleted and has no
// remaining finalizers, it is actually removed (the finalizer contract).
func (s *Store) Update(o *EdgeRoute) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if o.DeletionTimestamp && len(o.Finalizers) == 0 {
		delete(s.routes, o.Name)
		return
	}
	cp := *o
	cp.Finalizers = append([]string(nil), o.Finalizers...)
	s.routes[o.Name] = &cp
}

// Delete marks an EdgeRoute for deletion. If it has finalizers it stays
// (in Terminating) until they're removed; otherwise it's removed now.
func (s *Store) Delete(name string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	o, ok := s.routes[name]
	if !ok {
		return
	}
	o.DeletionTimestamp = true
	if len(o.Finalizers) == 0 {
		delete(s.routes, name)
	}
}

// Exists reports whether the object still exists (i.e. not finalized away).
func (s *Store) Exists(name string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	_, ok := s.routes[name]
	return ok
}

// ListLive returns EdgeRoutes that are NOT being deleted, sorted by name
// for deterministic data-plane config.
func (s *Store) ListLive() []*EdgeRoute {
	s.mu.Lock()
	defer s.mu.Unlock()
	var out []*EdgeRoute
	for _, o := range s.routes {
		if !o.DeletionTimestamp {
			cp := *o
			out = append(out, &cp)
		}
	}
	sortByName(out)
	return out
}

// AddService / RemoveService model the "actual state" the controller
// observes (does the target Service exist?).
func (s *Store) AddService(name string)    { s.mu.Lock(); s.services[name] = true; s.mu.Unlock() }
func (s *Store) RemoveService(name string) { s.mu.Lock(); delete(s.services, name); s.mu.Unlock() }
func (s *Store) HasService(name string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.services[name]
}

func sortByName(rs []*EdgeRoute) {
	for i := 1; i < len(rs); i++ {
		for j := i; j > 0 && rs[j-1].Name > rs[j].Name; j-- {
			rs[j-1], rs[j] = rs[j], rs[j-1]
		}
	}
}
