package interceptor

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/mattn/anko/env"
	"github.com/mattn/anko/vm"
)

type Ankointerceptor struct {
	importMap  map[string]interface{}
	libMap     map[string]interface{}
	libhabdles []uintptr
	ankoEnv    *env.Env
}

func NewAnkointerceptor(ankoEnv *env.Env) *Ankointerceptor {
	if ankoEnv == nil {
		ankoEnv = env.NewEnv()
	}
	a := &Ankointerceptor{
		importMap:  make(map[string]interface{}),
		libMap:     make(map[string]interface{}),
		libhabdles: make([]uintptr, 0),
		ankoEnv:    ankoEnv,
	}
	a.ankoEnv.Define("println", fmt.Println)
	a.ankoEnv.Define("loadLibrary", func(libPath string, libFuncMap map[string]funcType) interface{} {
		return a.loadLibrary(libPath, libFuncMap)
	})
	a.importMap["fs"] = &FileModule{}
	a.importMap["net"] = &NetModule{}
	a.importMap["process"] = &ProcessModule{}
	a.importMap["shell"] = &ShellModule{}
	return a
}

// Exec executes the specified script within the Anko environment.
// It sets up the environment with necessary variables and methods,
// reads the script file, and executes it, returning the result or an error.
//
// Parameters:
//   - script: Path to the script file to be executed.
//
// Returns:
//   - interface{}: The result of the script execution.
//   - error: An error if the script file cannot be read or execution fails.
func (a *Ankointerceptor) Exec(script string) (interface{}, error) {
	st, err := os.Stat(script)
	if err != nil {
		return nil, err
	}
	if st.IsDir() {
		toExec := filepath.Join(script, "index.ank")
		return a.exec(toExec, script)
	}
	if !filepath.IsAbs(script) {
		script = filepath.Join(os.Getenv("PWD"), script)
	}
	content, err := os.ReadFile(script)
	if err != nil {
		return nil, err
	}
	scriptContent := string(content)
	result, err := a.exec(scriptContent, script)
	if err != nil {
		return nil, err
	}
	return result, nil
}

// ExecContent executes the specified script content within the Anko environment.
// It sets up the environment with necessary variables and methods,
// executes the script content, and returns the result or an error.
//
// Parameters:
//   - scriptContent: The script content to be executed.
//
// Returns:
//   - interface{}: The result of the script execution.
//   - error: An error if the script execution fails.
func (a *Ankointerceptor) ExecContent(scriptContent string) (interface{}, error) {
	result, err := a.exec(scriptContent, "")
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (a *Ankointerceptor) exec(scriptContent string, scriptPath string) (interface{}, error) {
	e := a.ankoEnv.Copy()
	// Configure the Anko environment with required variables and methods
	if scriptPath == "" {
		cdir, _ := os.Getwd()
		scriptPath = filepath.Join(cdir, "tmp")
	}
	e.Define("Require", a.genRequireMethod(scriptPath))
	e.Define("__filename", scriptPath)
	e.Define("__dirname", filepath.Dir(scriptPath))

	// Execute the script code in the prepared environment
	result, err := vm.Execute(e, nil, scriptContent)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	return result, nil
}

// genRequireMethod 创建模块解析闭包函数
// 该函数用于处理模块路径解析和缓存机制：
// 1. 优先从importMap缓存中查找已加载模块
// 2. 对相对路径自动转换为基于basePath的绝对路径
// 3. 执行模块加载后自动缓存结果
// 参数:
//
//	basePath - 模块基础路径，用于解析相对路径
//
// 返回:
//
//	闭包函数，接收模块路径s，返回解析后的模块对象
func (a *Ankointerceptor) genRequireMethod(basePath string) interface{} {
	return func(s string) interface{} {
		if r, ok := a.importMap[s]; ok {
			return r
		}
		// 将相对路径转换为基于basePath的绝对路径
		if !filepath.IsAbs(s) {
			s = filepath.Join(filepath.Dir(basePath), s)
		}
		if _, ok := a.importMap[s]; ok {
			return a.importMap[s]
		}
		// 执行模块加载并将结果缓存
		result, err := a.Exec(s)
		if err != nil {
			panic(err)
		}
		a.importMap[s] = result
		return result
	}
}

func (a *Ankointerceptor) RegistModule(name string, m map[string]interface{}) {
	_, ok := a.importMap[name]
	if ok {
		panic("Module already exists")
	}
	a.importMap[name] = m
}

func (a *Ankointerceptor) RegistStructAsModule(name string, s interface{}) {
	_, ok := a.importMap[name]
	if ok {
		panic("Module already exists")
	}
	a.importMap[name] = s
}
