package llmprovider

import (
	"encoding/json"
	"fmt"

	"github.com/yaoapp/gou/connector"
)

// connectorID builds the runtime ID for registering into connector.Connectors.
// Dynamic providers get an owner prefix to avoid collision with builtin IDs.
func connectorID(p *Provider) string {
	switch p.Owner.Type {
	case "user":
		return "u" + p.Owner.UserID + "." + p.Key
	case "team":
		return "t" + p.Owner.TeamID + "." + p.Key
	default:
		return "s." + p.Key
	}
}

// defaultModel returns the first enabled model ID, or empty string.
func defaultModel(p *Provider) string {
	for _, m := range p.Models {
		if m.Enabled {
			return m.ID
		}
	}
	if len(p.Models) > 0 {
		return p.Models[0].ID
	}
	return ""
}

// marshalDSL builds a connector DSL JSON from the flat Provider fields.
func marshalDSL(p *Provider) ([]byte, error) {
	dsl := map[string]interface{}{
		"type":  p.Type,
		"name":  p.Name,
		"label": p.Name,
		"options": map[string]interface{}{
			"host":  p.APIURL,
			"key":   p.APIKey,
			"model": defaultModel(p),
		},
	}
	return json.Marshal(dsl)
}

// ensureConnector makes sure the provider's connector is registered in the runtime.
// Builtin providers are managed by engine.Load and skipped here.
func ensureConnector(p *Provider) error {
	if p.Source == ProviderSourceBuiltIn {
		return nil
	}
	if !p.Enabled {
		return nil
	}

	cid := p.ConnectorID
	if cid == "" {
		cid = connectorID(p)
	}

	if _, err := connector.Select(cid); err == nil {
		return nil
	}

	dslJSON, err := marshalDSL(p)
	if err != nil {
		return fmt.Errorf("ensureConnector %s: marshal DSL: %w", p.Key, err)
	}

	_, err = connector.LoadSourceSync(dslJSON, cid, "__registry/"+cid+".conn.yao")
	if err != nil {
		return fmt.Errorf("ensureConnector %s: LoadSourceSync: %w", p.Key, err)
	}

	return nil
}

// unregisterConnector removes the provider's connector from the runtime.
func unregisterConnector(p *Provider) error {
	if p.Source == ProviderSourceBuiltIn {
		return nil
	}
	cid := p.ConnectorID
	if cid == "" {
		cid = connectorID(p)
	}
	return connector.Unregister(cid)
}

// importFromConnectors scans existing AI connectors loaded by engine.Load
// and imports them as builtin providers into the Registry store.
// If a store record with the same key already exists (dynamic), it is not overwritten.
func importFromConnectors(r *Registry) error {
	for _, opt := range connector.AIConnectors {
		id := opt.Value
		if r.store.Has(storeKey(id)) {
			continue
		}

		conn, err := connector.Select(id)
		if err != nil {
			continue
		}

		p := providerFromConnector(id, conn)
		m, err := providerToMap(&p, r.encKey)
		if err != nil {
			continue
		}
		sk := storeKey(id)
		_ = r.store.Set(sk, m, 0)
		if r.cache != nil {
			_ = r.cache.Set(sk, m, 0)
		}
		_ = indexAdd(r.store, r.cache, id)
	}
	return nil
}

// providerFromConnector builds a Provider from a runtime Connector interface.
func providerFromConnector(id string, conn connector.Connector) Provider {
	meta := conn.GetMetaInfo()
	setting := conn.Setting()

	name := meta.Label
	if name == "" {
		name = id
	}

	typ := connectorType(conn)
	apiURL, _ := setting["host"].(string)
	apiKey, _ := setting["key"].(string)
	model, _ := setting["model"].(string)

	var models []ModelInfo
	if model != "" {
		caps := capabilitiesFromSetting(setting)
		models = []ModelInfo{{
			ID:           model,
			Name:         model,
			Capabilities: caps,
			Enabled:      true,
		}}
	}

	return Provider{
		Key:         id,
		ConnectorID: id,
		Name:        name,
		Type:        typ,
		APIURL:      apiURL,
		APIKey:      apiKey,
		Models:      models,
		Enabled:     true,
		Status:      "connected",
		Source:      ProviderSourceBuiltIn,
		Owner:       ProviderOwner{Type: "system"},
	}
}

func connectorType(conn connector.Connector) string {
	switch {
	case conn.Is(6): // OPENAI
		return "openai"
	case conn.Is(11): // ANTHROPIC
		return "anthropic"
	case conn.Is(9): // FASTEMBED
		return "fastembed"
	case conn.Is(8): // MOAPI
		return "moapi"
	default:
		return "custom"
	}
}

func capabilitiesFromSetting(setting map[string]interface{}) []string {
	raw, ok := setting["capabilities"]
	if !ok {
		return nil
	}

	switch caps := raw.(type) {
	case map[string]interface{}:
		var out []string
		for k, v := range caps {
			if b, ok := v.(bool); ok && b {
				out = append(out, k)
			}
		}
		return out
	default:
		return nil
	}
}
