package adapters

import (
	"fmt"

	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/structpb"

	"iop/apps/node/internal/adapters/cli"
	"iop/apps/node/internal/adapters/mock"
	"iop/apps/node/internal/adapters/ollama"
	"iop/apps/node/internal/adapters/vllm"
	"iop/packages/go/config"
	iop "iop/proto/gen/iop"
)

// BuildFromPayload creates a Registry from a NodeConfigPayload received from edge.
func BuildFromPayload(payload *iop.NodeConfigPayload, logger *zap.Logger) (*Registry, error) {
	reg := NewRegistry()

	for _, ac := range payload.GetAdapters() {
		if !ac.GetEnabled() {
			continue
		}
		instanceKey := ac.GetName()
		typeName := ac.GetType()
		switch typeName {
		case "mock":
			reg.RegisterKeyed(instanceKey, typeName, mock.New(logger))
		case "ollama":
			var cfg config.OllamaConf
			if m := ac.GetOllama(); m != nil {
				cfg = ollamaConfFromProto(m)
			} else {
				cfg = ollamaConfFromStruct(ac.GetSettings())
			}
			reg.RegisterKeyed(instanceKey, typeName, ollama.New(cfg, logger, instanceKey))
		case "vllm":
			var cfg config.VllmConf
			if m := ac.GetVllm(); m != nil {
				cfg = vllmConfFromProto(m)
			} else {
				cfg = vllmConfFromStruct(ac.GetSettings())
			}
			reg.RegisterKeyed(instanceKey, typeName, vllm.New(cfg, logger, instanceKey))
		case "cli":
			var cfg config.CLIConf
			if m := ac.GetCli(); m != nil {
				cfg = cliConfFromProto(m)
			} else {
				cfg = cliConfFromStruct(ac.GetSettings())
			}
			reg.RegisterKeyed(instanceKey, typeName, cli.New(cfg, logger))
		default:
			return nil, fmt.Errorf("adapters: unknown adapter type %q", typeName)
		}
	}
	return reg, nil
}

func ollamaConfFromProto(m *iop.OllamaAdapterConfig) config.OllamaConf {
	return config.OllamaConf{
		Enabled:     true,
		BaseURL:     m.GetBaseUrl(),
		ContextSize: int(m.GetContextSize()),
	}
}

func vllmConfFromProto(m *iop.VllmAdapterConfig) config.VllmConf {
	return config.VllmConf{Enabled: true, Endpoint: m.GetEndpoint()}
}

func cliConfFromProto(m *iop.CLIAdapterConfig) config.CLIConf {
	cfg := config.CLIConf{Enabled: true, Profiles: make(map[string]config.CLIProfileConf)}
	for name, p := range m.GetProfiles() {
		cfg.Profiles[name] = cliProfileFromProto(p)
	}
	return cfg
}

func cliProfileFromProto(p *iop.CLIProfileConfig) config.CLIProfileConf {
	if p == nil {
		return config.CLIProfileConf{}
	}
	prof := config.CLIProfileConf{
		Command:               p.GetCommand(),
		Args:                  append([]string(nil), p.GetArgs()...),
		Env:                   append([]string(nil), p.GetEnv()...),
		Persistent:            p.GetPersistent(),
		Terminal:              p.GetTerminal(),
		ResponseIdleTimeoutMS: int(p.GetResponseIdleTimeoutMs()),
		StartupIdleTimeoutMS:  int(p.GetStartupIdleTimeoutMs()),
		OutputFormat:          p.GetOutputFormat(),
		Mode:                  p.GetMode(),
		ResumeArgs:            append([]string(nil), p.GetResumeArgs()...),
	}
	if marker := p.GetCompletionMarker(); marker != nil {
		prof.CompletionMarker = config.CompletionMarkerConf{
			Line:  marker.GetLine(),
			Regex: marker.GetRegex(),
		}
	}
	return prof
}

// Legacy structpb fallback paths for payloads from older edge versions
// that populate AdapterConfig.settings instead of the typed oneof.

func ollamaConfFromStruct(s *structpb.Struct) config.OllamaConf {
	cfg := config.OllamaConf{Enabled: true}
	if s == nil {
		return cfg
	}
	m := s.AsMap()
	if v, ok := m["base_url"].(string); ok {
		cfg.BaseURL = v
	}
	if v, ok := intSetting(m, "context_size"); ok {
		cfg.ContextSize = v
	} else if v, ok := intSetting(m, "num_ctx"); ok {
		cfg.ContextSize = v
	}
	return cfg
}

func intSetting(m map[string]any, key string) (int, bool) {
	v, ok := m[key]
	if !ok {
		return 0, false
	}
	switch n := v.(type) {
	case int:
		return n, true
	case int32:
		return int(n), true
	case int64:
		return int(n), true
	case float64:
		return int(n), true
	default:
		return 0, false
	}
}

func vllmConfFromStruct(s *structpb.Struct) config.VllmConf {
	cfg := config.VllmConf{Enabled: true}
	if s == nil {
		return cfg
	}
	m := s.AsMap()
	if v, ok := m["endpoint"].(string); ok {
		cfg.Endpoint = v
	}
	return cfg
}

func cliConfFromStruct(s *structpb.Struct) config.CLIConf {
	cfg := config.CLIConf{Enabled: true, Profiles: make(map[string]config.CLIProfileConf)}
	if s == nil {
		return cfg
	}
	m := s.AsMap()
	profiles, ok := m["profiles"].(map[string]any)
	if !ok {
		return cfg
	}
	for name, pAny := range profiles {
		p, ok := pAny.(map[string]any)
		if !ok {
			continue
		}
		prof := config.CLIProfileConf{}
		if v, ok := p["command"].(string); ok {
			prof.Command = v
		}
		prof.Args = stringsFromAny(p["args"])
		prof.Env = stringsFromAny(p["env"])
		if v, ok := boolFromAny(p["persistent"]); ok {
			prof.Persistent = v
		}
		if v, ok := boolFromAny(p["terminal"]); ok {
			prof.Terminal = v
		}
		if v, ok := intFromAny(p["response_idle_timeout_ms"]); ok {
			prof.ResponseIdleTimeoutMS = v
		}
		if v, ok := intFromAny(p["startup_idle_timeout_ms"]); ok {
			prof.StartupIdleTimeoutMS = v
		}
		if v, ok := p["output_format"].(string); ok {
			prof.OutputFormat = v
		}
		if v, ok := p["mode"].(string); ok {
			prof.Mode = v
		}
		prof.ResumeArgs = stringsFromAny(p["resume_args"])
		if marker, ok := p["completion_marker"].(map[string]any); ok {
			if v, ok := marker["line"].(string); ok {
				prof.CompletionMarker.Line = v
			}
			if v, ok := marker["regex"].(string); ok {
				prof.CompletionMarker.Regex = v
			}
		}
		cfg.Profiles[name] = prof
	}
	return cfg
}

func stringsFromAny(v any) []string {
	arr, ok := v.([]any)
	if !ok {
		return nil
	}
	out := make([]string, 0, len(arr))
	for _, a := range arr {
		if s, ok := a.(string); ok {
			out = append(out, s)
		}
	}
	return out
}

func boolFromAny(v any) (bool, bool) {
	if b, ok := v.(bool); ok {
		return b, true
	}
	return false, false
}

func intFromAny(v any) (int, bool) {
	if f, ok := v.(float64); ok {
		return int(f), true
	}
	if i, ok := v.(int); ok {
		return i, true
	}
	return 0, false
}
