package openrpc

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"

	"git.threefold.info/herocode/heroagent/pkg/openrpc/models"
)

// ORPCManager manages OpenRPC specifications
type ORPCManager struct {
	specs map[string]*models.OpenRPCSpec
}

// NewORPCManager creates a new OpenRPC Manager
func NewORPCManager() *ORPCManager {
	return &ORPCManager{
		specs: make(map[string]*models.OpenRPCSpec),
	}
}

// LoadSpecs loads all OpenRPC specifications from a directory
func (m *ORPCManager) LoadSpecs(dir string) error {
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return fmt.Errorf("failed to read directory: %w", err)
	}

	for _, file := range files {
		if file.IsDir() {
			continue
		}

		if !strings.HasSuffix(file.Name(), ".json") {
			continue
		}

		path := filepath.Join(dir, file.Name())
		if err := m.LoadSpec(path); err != nil {
			return fmt.Errorf("failed to load spec %s: %w", file.Name(), err)
		}
	}

	return nil
}

// LoadSpec loads an OpenRPC specification from a file
func (m *ORPCManager) LoadSpec(path string) error {
	// Read the file
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return fmt.Errorf("failed to read file: %w", err)
	}

	// Parse the JSON
	var spec models.OpenRPCSpec
	if err := json.Unmarshal(data, &spec); err != nil {
		return fmt.Errorf("failed to parse JSON: %w", err)
	}

	// Validate the specification
	if err := spec.Validate(); err != nil {
		return fmt.Errorf("invalid specification: %w", err)
	}

	// Store the specification
	name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
	m.specs[name] = &spec

	return nil
}

// GetSpec returns an OpenRPC specification by name
func (m *ORPCManager) GetSpec(name string) *models.OpenRPCSpec {
	return m.specs[name]
}

// ListSpecs returns a list of all loaded specification names
func (m *ORPCManager) ListSpecs() []string {
	var names []string
	for name := range m.specs {
		names = append(names, name)
	}
	return names
}

// ListMethods returns a list of all method names in a specification
func (m *ORPCManager) ListMethods(specName string) []string {
	spec := m.GetSpec(specName)
	if spec == nil {
		return []string{}
	}

	var methods []string
	for _, method := range spec.Methods {
		methods = append(methods, method.Name)
	}
	return methods
}

// GetMethod returns a method from a specification
func (m *ORPCManager) GetMethod(specName, methodName string) *models.Method {
	spec := m.GetSpec(specName)
	if spec == nil {
		return nil
	}

	for _, method := range spec.Methods {
		if method.Name == methodName {
			return &method
		}
	}
	return nil
}
