// Package registry manages function metadata in RQLite and bytecode in IPFS.
package registry

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/DeBrosOfficial/network/pkg/ipfs"
	"github.com/DeBrosOfficial/network/pkg/rqlite"
	"go.uber.org/zap"
)

// Ensure Registry implements FunctionRegistry interface.
var _ FunctionRegistry = (*Registry)(nil)

// Registry coordinates between function storage, IPFS storage, and logging.
type Registry struct {
	functionStore    *FunctionStore
	ipfsStore        *IPFSStore
	invocationLogger *InvocationLogger
	logger           *zap.Logger
}

// NewRegistry creates a new function registry.
func NewRegistry(db rqlite.Client, ipfsClient ipfs.IPFSClient, cfg RegistryConfig, logger *zap.Logger) *Registry {
	return &Registry{
		functionStore:    NewFunctionStore(db, logger),
		ipfsStore:        NewIPFSStore(ipfsClient, cfg.IPFSAPIURL, logger),
		invocationLogger: NewInvocationLogger(db, logger),
		logger:           logger,
	}
}

// Register deploys a new function or updates an existing one.
func (r *Registry) Register(ctx context.Context, fn *FunctionDefinition, wasmBytes []byte) (*Function, error) {
	if fn == nil {
		return nil, &ValidationError{Field: "definition", Message: "cannot be nil"}
	}
	fn.Name = strings.TrimSpace(fn.Name)
	fn.Namespace = strings.TrimSpace(fn.Namespace)

	if fn.Name == "" {
		return nil, &ValidationError{Field: "name", Message: "cannot be empty"}
	}
	if fn.Namespace == "" {
		return nil, &ValidationError{Field: "namespace", Message: "cannot be empty"}
	}
	if len(wasmBytes) == 0 {
		return nil, &ValidationError{Field: "wasmBytes", Message: "cannot be empty"}
	}

	oldFn, err := r.functionStore.GetByNameInternal(ctx, fn.Namespace, fn.Name)
	if err != nil && err != ErrFunctionNotFound {
		return nil, &DeployError{FunctionName: fn.Name, Cause: err}
	}

	wasmCID, err := r.ipfsStore.Upload(ctx, wasmBytes, fn.Name)
	if err != nil {
		return nil, &DeployError{FunctionName: fn.Name, Cause: err}
	}

	savedFunc, err := r.functionStore.Save(ctx, fn, wasmCID, oldFn)
	if err != nil {
		return nil, &DeployError{FunctionName: fn.Name, Cause: err}
	}

	if err := r.functionStore.SaveEnvVars(ctx, savedFunc.ID, fn.EnvVars); err != nil {
		return nil, &DeployError{FunctionName: fn.Name, Cause: err}
	}

	r.logger.Info("Function registered",
		zap.String("id", savedFunc.ID),
		zap.String("name", fn.Name),
		zap.String("namespace", fn.Namespace),
		zap.String("wasm_cid", wasmCID),
		zap.Int("version", savedFunc.Version),
		zap.Bool("updated", oldFn != nil),
	)

	return oldFn, nil
}

// Get retrieves a function by name and optional version.
func (r *Registry) Get(ctx context.Context, namespace, name string, version int) (*Function, error) {
	return r.functionStore.Get(ctx, namespace, name, version)
}

// List returns all functions for a namespace.
func (r *Registry) List(ctx context.Context, namespace string) ([]*Function, error) {
	return r.functionStore.List(ctx, namespace)
}

// Delete removes a function. If version is 0, removes all versions.
func (r *Registry) Delete(ctx context.Context, namespace, name string, version int) error {
	return r.functionStore.Delete(ctx, namespace, name, version)
}

// SetEnabled flips a function's enabled state across all versions
// (plan 11.5). Thin pass-through to FunctionStore.SetStatus.
func (r *Registry) SetEnabled(ctx context.Context, namespace, name string, enabled bool) error {
	status := FunctionStatusInactive
	if enabled {
		status = FunctionStatusActive
	}
	return r.functionStore.SetStatus(ctx, namespace, name, status)
}

// GetWASMBytes retrieves the compiled WASM bytecode for a function.
func (r *Registry) GetWASMBytes(ctx context.Context, wasmCID string) ([]byte, error) {
	if wasmCID == "" {
		return nil, &ValidationError{Field: "wasmCID", Message: "cannot be empty"}
	}
	return r.ipfsStore.Get(ctx, wasmCID)
}

// GetLogs retrieves WASM-emitted log entries for a function. Most functions
// don't call log_info / log_error, so this is often empty — see
// GetInvocations for the always-populated invocation-history view.
func (r *Registry) GetLogs(ctx context.Context, namespace, name string, limit int) ([]LogEntry, error) {
	return r.invocationLogger.GetLogs(ctx, namespace, name, limit)
}

// GetInvocations returns the invocation-history view for a function:
// timestamp / status / duration / error_message per invocation, with any
// associated WASM log entries nested per record.
func (r *Registry) GetInvocations(ctx context.Context, namespace, name string, limit int) ([]Invocation, error) {
	return r.invocationLogger.GetInvocations(ctx, namespace, name, limit)
}

// GetEnvVars retrieves environment variables for a function.
func (r *Registry) GetEnvVars(ctx context.Context, functionID string) (map[string]string, error) {
	return r.functionStore.GetEnvVars(ctx, functionID)
}

// GetByID retrieves a function by its ID.
func (r *Registry) GetByID(ctx context.Context, id string) (*Function, error) {
	return r.functionStore.GetByID(ctx, id)
}

// ListVersions returns all versions of a function.
func (r *Registry) ListVersions(ctx context.Context, namespace, name string) ([]*Function, error) {
	return r.functionStore.ListVersions(ctx, namespace, name)
}

// LogInvocation records a function invocation and its logs to the database.
func (r *Registry) LogInvocation(ctx context.Context,
	id, functionID, requestID string,
	triggerType interface{},
	callerWallet string,
	inputSize, outputSize int,
	startedAt, completedAt interface{},
	durationMS int64,
	status interface{},
	errorMessage string,
	memoryUsedMB float64,
	logs []LogEntry) error {

	var startTime, completeTime time.Time
	if t, ok := startedAt.(time.Time); ok {
		startTime = t
	}
	if t, ok := completedAt.(time.Time); ok {
		completeTime = t
	}

	data := &InvocationRecordData{
		ID:           id,
		FunctionID:   functionID,
		RequestID:    requestID,
		TriggerType:  fmt.Sprintf("%v", triggerType),
		CallerWallet: callerWallet,
		InputSize:    inputSize,
		OutputSize:   outputSize,
		StartedAt:    startTime,
		CompletedAt:  completeTime,
		DurationMS:   durationMS,
		Status:       fmt.Sprintf("%v", status),
		ErrorMessage: errorMessage,
		MemoryUsedMB: memoryUsedMB,
	}

	data.Logs = make([]LogData, len(logs))
	for i, log := range logs {
		data.Logs[i] = LogData{
			Level:     log.Level,
			Message:   log.Message,
			Timestamp: log.Timestamp,
		}
	}

	return r.invocationLogger.Log(ctx, data)
}
