package context

import (
	"context"

	"github.com/yaoapp/gou/runtime/v8/bridge"
	openapiSandbox "github.com/yaoapp/yao/openapi/sandbox"
	infraSandbox "github.com/yaoapp/yao/sandbox"
	"rogchap.com/v8go"
)

// SandboxExecutor defines the interface for sandbox operations
// This interface is implemented by agent/sandbox.Executor
// It's defined here to avoid import cycles
type SandboxExecutor interface {
	// Filesystem operations
	ReadFile(ctx context.Context, path string) ([]byte, error)
	WriteFile(ctx context.Context, path string, content []byte) error
	ListDir(ctx context.Context, path string) ([]infraSandbox.FileInfo, error)

	// Command execution
	Exec(ctx context.Context, cmd []string) (string, error)

	// Workspace info
	GetWorkDir() string

	// Sandbox identification
	GetSandboxID() string

	// VNC access (returns empty string if not available)
	GetVNCUrl() string
}

// SetSandboxExecutor sets the sandbox executor for this context
// This should be called before hooks are executed
func (ctx *Context) SetSandboxExecutor(executor SandboxExecutor) {
	ctx.sandboxExecutor = executor
}

// GetSandboxExecutor returns the sandbox executor if available
func (ctx *Context) GetSandboxExecutor() SandboxExecutor {
	return ctx.sandboxExecutor
}

// HasSandbox returns true if sandbox executor is available
func (ctx *Context) HasSandbox() bool {
	return ctx.sandboxExecutor != nil
}

// newSandboxObject creates the ctx.sandbox JavaScript object
// Returns nil if sandbox executor is not available
func (ctx *Context) newSandboxObject(iso *v8go.Isolate) *v8go.ObjectTemplate {
	if ctx.sandboxExecutor == nil {
		return nil
	}

	sandboxObj := v8go.NewObjectTemplate(iso)

	// Set methods
	sandboxObj.Set("ReadFile", ctx.sandboxReadFileMethod(iso))
	sandboxObj.Set("WriteFile", ctx.sandboxWriteFileMethod(iso))
	sandboxObj.Set("ListDir", ctx.sandboxListDirMethod(iso))
	sandboxObj.Set("Exec", ctx.sandboxExecMethod(iso))
	sandboxObj.Set("GetVNCUrl", ctx.sandboxGetVNCUrlMethod(iso))
	sandboxObj.Set("GetSandboxID", ctx.sandboxGetSandboxIDMethod(iso))

	return sandboxObj
}

// createSandboxInstance creates the sandbox object instance with workdir property
func (ctx *Context) createSandboxInstance(v8ctx *v8go.Context) *v8go.Value {
	if ctx.sandboxExecutor == nil {
		return nil
	}

	sandboxTemplate := ctx.newSandboxObject(v8ctx.Isolate())
	if sandboxTemplate == nil {
		return nil
	}

	// Set workdir as a property
	sandboxTemplate.Set("workdir", ctx.sandboxExecutor.GetWorkDir())

	// Set sandbox_id as a property
	sandboxID := ctx.sandboxExecutor.GetSandboxID()
	sandboxTemplate.Set("sandbox_id", sandboxID)

	// Set vnc_url as a property (empty string if not available)
	// GetVNCUrl returns sandbox ID if VNC is supported, empty otherwise
	vncSandboxID := ctx.sandboxExecutor.GetVNCUrl()
	if vncSandboxID != "" {
		sandboxTemplate.Set("vnc_url", openapiSandbox.GetVNCClientURL(vncSandboxID))
	} else {
		sandboxTemplate.Set("vnc_url", "")
	}

	instance, err := sandboxTemplate.NewInstance(v8ctx)
	if err != nil {
		return nil
	}

	return instance.Value
}

// sandboxReadFileMethod implements ctx.sandbox.ReadFile(path)
func (ctx *Context) sandboxReadFileMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
	return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
		v8ctx := info.Context()
		args := info.Args()

		if ctx.sandboxExecutor == nil {
			return bridge.JsException(v8ctx, "sandbox executor not available")
		}

		if len(args) < 1 {
			return bridge.JsException(v8ctx, "ReadFile requires path parameter")
		}

		path := args[0].String()

		content, err := ctx.sandboxExecutor.ReadFile(context.Background(), path)
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		// Return as string
		jsVal, err := v8go.NewValue(iso, string(content))
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		return jsVal
	})
}

// sandboxWriteFileMethod implements ctx.sandbox.WriteFile(path, content)
func (ctx *Context) sandboxWriteFileMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
	return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
		v8ctx := info.Context()
		args := info.Args()

		if ctx.sandboxExecutor == nil {
			return bridge.JsException(v8ctx, "sandbox executor not available")
		}

		if len(args) < 2 {
			return bridge.JsException(v8ctx, "WriteFile requires path and content parameters")
		}

		path := args[0].String()
		content := args[1].String()

		err := ctx.sandboxExecutor.WriteFile(context.Background(), path, []byte(content))
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		// Return undefined on success
		return v8go.Undefined(iso)
	})
}

// sandboxListDirMethod implements ctx.sandbox.ListDir(path)
func (ctx *Context) sandboxListDirMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
	return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
		v8ctx := info.Context()
		args := info.Args()

		if ctx.sandboxExecutor == nil {
			return bridge.JsException(v8ctx, "sandbox executor not available")
		}

		if len(args) < 1 {
			return bridge.JsException(v8ctx, "ListDir requires path parameter")
		}

		path := args[0].String()

		files, err := ctx.sandboxExecutor.ListDir(context.Background(), path)
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		// Convert to JavaScript array of objects
		result := make([]map[string]interface{}, len(files))
		for i, f := range files {
			result[i] = map[string]interface{}{
				"name":   f.Name,
				"size":   f.Size,
				"is_dir": f.IsDir,
			}
		}

		jsVal, err := bridge.JsValue(v8ctx, result)
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		return jsVal
	})
}

// sandboxExecMethod implements ctx.sandbox.Exec(cmd)
func (ctx *Context) sandboxExecMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
	return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
		v8ctx := info.Context()
		args := info.Args()

		if ctx.sandboxExecutor == nil {
			return bridge.JsException(v8ctx, "sandbox executor not available")
		}

		if len(args) < 1 {
			return bridge.JsException(v8ctx, "Exec requires cmd parameter (array of strings)")
		}

		// Parse command array
		cmdArg := args[0]
		if !cmdArg.IsArray() {
			return bridge.JsException(v8ctx, "Exec requires cmd to be an array of strings")
		}

		cmdObj, err := cmdArg.AsObject()
		if err != nil {
			return bridge.JsException(v8ctx, "failed to parse cmd array: "+err.Error())
		}

		// Get array length
		lengthVal, err := cmdObj.Get("length")
		if err != nil {
			return bridge.JsException(v8ctx, "failed to get cmd array length: "+err.Error())
		}
		length := int(lengthVal.Integer())

		// Build command slice
		cmd := make([]string, length)
		for i := 0; i < length; i++ {
			itemVal, err := cmdObj.GetIdx(uint32(i))
			if err != nil {
				return bridge.JsException(v8ctx, "failed to get cmd array element: "+err.Error())
			}
			cmd[i] = itemVal.String()
		}

		output, err := ctx.sandboxExecutor.Exec(context.Background(), cmd)
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		jsVal, err := v8go.NewValue(v8ctx.Isolate(), output)
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		return jsVal
	})
}

// sandboxGetVNCUrlMethod implements ctx.sandbox.GetVNCUrl()
func (ctx *Context) sandboxGetVNCUrlMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
	return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
		v8ctx := info.Context()

		if ctx.sandboxExecutor == nil {
			return bridge.JsException(v8ctx, "sandbox executor not available")
		}

		// GetVNCUrl returns sandbox ID if VNC is supported, empty otherwise
		vncSandboxID := ctx.sandboxExecutor.GetVNCUrl()
		vncUrl := ""
		if vncSandboxID != "" {
			vncUrl = openapiSandbox.GetVNCClientURL(vncSandboxID)
		}

		jsVal, err := v8go.NewValue(iso, vncUrl)
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		return jsVal
	})
}

// sandboxGetSandboxIDMethod implements ctx.sandbox.GetSandboxID()
func (ctx *Context) sandboxGetSandboxIDMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
	return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
		v8ctx := info.Context()

		if ctx.sandboxExecutor == nil {
			return bridge.JsException(v8ctx, "sandbox executor not available")
		}

		sandboxID := ctx.sandboxExecutor.GetSandboxID()

		jsVal, err := v8go.NewValue(iso, sandboxID)
		if err != nil {
			return bridge.JsException(v8ctx, err.Error())
		}

		return jsVal
	})
}
