// Package sandbox — modules.go
//
// v0.29.0 CS3: Module factories for Starlark sandbox.
// Each factory returns a starlarkstruct.Module that the runner
// injects into the script namespace based on granted permissions.
package sandbox

import (
	"context"
	"fmt"

	"go.starlark.net/starlark"
	"go.starlark.net/starlarkstruct"

	"switchboard-core/models"
	"switchboard-core/store"
)

// ─── Secrets Module ──────────────────────────
//
// Requires permission: secrets.read
// Starlark API:
//   val = secrets.get("api_key")     → string or None
//   all = secrets.list()             → list of key names
//
// Secrets are stored in GlobalConfig under key "ext_secrets:{packageID}"
// as a JSON map: {"api_key": "sk-...", "webhook_token": "tok-..."}
// Admin sets them via PUT /admin/extensions/:id/secrets.

// BuildSecretsModule creates the "secrets" module scoped to a package.
func BuildSecretsModule(ctx context.Context, stores store.Stores, packageID string) *starlarkstruct.Module {
	// Pre-load secrets for this package (single query, not per-call)
	secretMap := loadExtensionSecrets(ctx, stores, packageID)

	return MakeModule("secrets", starlark.StringDict{
		"get": starlark.NewBuiltin("secrets.get", func(
			thread *starlark.Thread, b *starlark.Builtin,
			args starlark.Tuple, kwargs []starlark.Tuple,
		) (starlark.Value, error) {
			var key string
			if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &key); err != nil {
				return nil, err
			}
			if val, ok := secretMap[key]; ok {
				return starlark.String(val), nil
			}
			return starlark.None, nil
		}),
		"list": starlark.NewBuiltin("secrets.list", func(
			thread *starlark.Thread, b *starlark.Builtin,
			args starlark.Tuple, kwargs []starlark.Tuple,
		) (starlark.Value, error) {
			keys := make([]starlark.Value, 0, len(secretMap))
			for k := range secretMap {
				keys = append(keys, starlark.String(k))
			}
			return starlark.NewList(keys), nil
		}),
	})
}

func loadExtensionSecrets(ctx context.Context, stores store.Stores, packageID string) map[string]string {
	result := make(map[string]string)
	configKey := "ext_secrets:" + packageID
	configVal, err := stores.GlobalConfig.Get(ctx, configKey)
	if err != nil || configVal == nil {
		return result
	}
	for k, v := range configVal {
		if s, ok := v.(string); ok {
			result[k] = s
		}
	}
	return result
}

// ─── Notifications Module ────────────────────
//
// Requires permission: notifications.send
// Starlark API:
//   notifications.send(user_id, title, body="", type="extension.notify")
//
// Creates an in-app notification via the notification store.
// The extension cannot send email or bypass user preferences —
// that's handled by the notification service layer.

// NotificationSender is the interface the notifications module needs.
// Matches notifications.Service.Notify without importing the package.
type NotificationSender interface {
	Notify(ctx context.Context, n *models.Notification) error
}

// BuildNotificationsModule creates the "notifications" module scoped to a package.
func BuildNotificationsModule(ctx context.Context, sender NotificationSender, packageID string) *starlarkstruct.Module {
	return MakeModule("notifications", starlark.StringDict{
		"send": starlark.NewBuiltin("notifications.send", func(
			thread *starlark.Thread, b *starlark.Builtin,
			args starlark.Tuple, kwargs []starlark.Tuple,
		) (starlark.Value, error) {
			var userID, title string
			var body string
			var notifType string = "extension.notify"

			if err := starlark.UnpackArgs(b.Name(), args, kwargs,
				"user_id", &userID,
				"title", &title,
				"body?", &body,
				"type?", &notifType,
			); err != nil {
				return nil, err
			}

			if userID == "" {
				return nil, fmt.Errorf("notifications.send: user_id is required")
			}
			if title == "" {
				return nil, fmt.Errorf("notifications.send: title is required")
			}

			n := &models.Notification{
				UserID:       userID,
				Type:         notifType,
				Title:        title,
				Body:         body,
				ResourceType: "extension",
				ResourceID:   packageID,
			}

			if err := sender.Notify(ctx, n); err != nil {
				return nil, fmt.Errorf("notifications.send failed: %w", err)
			}

			return starlark.True, nil
		}),
	})
}
