package plugins

import (
	"encoding/json"
	"errors"
	"fmt"
	"runtime"
	"strings"
)

type Runtime string

const (
	RuntimeBuiltin  Runtime = "builtin"
	RuntimeHTTP     Runtime = "http"
	RuntimeGRPC     Runtime = "grpc"
	RuntimeProcess  Runtime = "process"
	RuntimeJSONRPC  Runtime = "jsonrpc-stdio"
	RuntimeDisabled Runtime = "disabled"
)

const (
	CapabilitySMS          = "sms"
	CapabilityEmail        = "email"
	CapabilityVerification = "verification"
	CapabilityProvision    = "provision"
	CapabilityPayment      = "payment"
	CapabilityAuth         = "auth"

	MethodSMSSend                  = "sms.send"
	MethodEmailSend                = "email.send"
	MethodVerificationVerify       = "verification.verify"
	MethodVerificationStartSession = "verification.start_session"
	MethodVerificationQueryResult  = "verification.query_result"
	MethodVerificationHandleNotify = "verification.handle_notify"
	MethodProvisionCreate          = "provision.create"
	MethodProvisionManage          = "provision.manage"
	MethodProvisionProductFields   = "provision.product_fields"
	MethodProvisionProductConfig   = "provision.product_config"
	MethodProvisionInstanceView    = "provision.instance_view"
	MethodPaymentCreate            = "payment.create"
	MethodPaymentNotify            = "payment.notify"
	MethodAuthGetOAuthURL          = "auth.get_oauth_url"
	MethodAuthExchangeCode         = "auth.exchange_code"
)

var (
	ErrNotFound           = errors.New("plugin not found")
	ErrCapabilityMissing  = errors.New("plugin capability missing")
	ErrRuntimeUnsupported = errors.New("plugin runtime unsupported")
	ErrInvalidManifest    = errors.New("invalid plugin manifest")
	ErrDisabled           = errors.New("plugin disabled")
)

type Manifest struct {
	SchemaVersion       int               `json:"schema_version"`
	ID                  string            `json:"id"`
	Type                string            `json:"type,omitempty"`
	Name                string            `json:"name"`
	Version             string            `json:"version"`
	Runtime             Runtime           `json:"runtime"`
	Endpoint            string            `json:"endpoint,omitempty"`
	Entry               map[string]string `json:"entry,omitempty"`
	Provider            string            `json:"provider,omitempty"`
	Capabilities        []string          `json:"capabilities,omitempty"`
	Description         string            `json:"description,omitempty"`
	ConfigSchema        map[string]any    `json:"config_schema,omitempty"`
	ManageActions       []map[string]any  `json:"manage_actions,omitempty"`
	TestLinks           []map[string]any  `json:"test_links,omitempty"`
	ProductPage         map[string]any    `json:"product_page,omitempty"`
	ManagePage          map[string]any    `json:"manage_page,omitempty"`
	UpstreamParamFields []map[string]any  `json:"upstream_param_fields,omitempty"`
	Requirements        map[string]any    `json:"requirements,omitempty"`
	Permissions         []string          `json:"permissions,omitempty"`
	Docs                string            `json:"docs,omitempty"`
	Author              map[string]any    `json:"author,omitempty"`
	Enabled             bool              `json:"enabled"`
	Status              string            `json:"status,omitempty"`
	Dir                 string            `json:"-"`
}

func (m Manifest) Validate() error {
	if strings.TrimSpace(m.ID) == "" || !SafeID(m.ID) {
		return fmt.Errorf("%w: id", ErrInvalidManifest)
	}
	if strings.TrimSpace(m.Name) == "" {
		return fmt.Errorf("%w: name", ErrInvalidManifest)
	}
	if strings.TrimSpace(m.Version) == "" {
		return fmt.Errorf("%w: version", ErrInvalidManifest)
	}
	if m.Runtime == "" {
		return fmt.Errorf("%w: runtime", ErrInvalidManifest)
	}
	if len(m.Capabilities) == 0 && strings.TrimSpace(m.Type) != "" {
		m.Capabilities = []string{m.Type}
	}
	return nil
}

func (m Manifest) HasCapability(capability string) bool {
	capability = strings.TrimSpace(capability)
	if capability == "" {
		return true
	}
	for _, item := range m.Capabilities {
		if strings.EqualFold(strings.TrimSpace(item), capability) {
			return true
		}
	}
	if strings.EqualFold(strings.TrimSpace(m.Type), capability) {
		return true
	}
	return false
}

func (m Manifest) EntryForCurrentPlatform() (string, error) {
	key := runtime.GOOS + "-" + runtime.GOARCH
	if m.Entry != nil {
		if value := strings.TrimSpace(m.Entry[key]); value != "" {
			return value, nil
		}
		if value := strings.TrimSpace(m.Entry[runtime.GOOS]); value != "" {
			return value, nil
		}
		if value := strings.TrimSpace(m.Entry["default"]); value != "" {
			return value, nil
		}
	}
	if strings.TrimSpace(m.Endpoint) != "" {
		return strings.TrimSpace(m.Endpoint), nil
	}
	return "", fmt.Errorf("%w: entry for %s", ErrInvalidManifest, key)
}

func SafeID(value string) bool {
	if value == "" {
		return false
	}
	for _, r := range value {
		if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '_' || r == '-' || r == '.' {
			continue
		}
		return false
	}
	return true
}

func PluginIDFromProvider(value string) string {
	value = strings.TrimSpace(value)
	for _, prefix := range []string{"plugin:", "qplug:", "process:", "stdio:"} {
		if strings.HasPrefix(value, prefix) {
			id := strings.TrimSpace(strings.TrimPrefix(value, prefix))
			if SafeID(id) {
				return id
			}
			return ""
		}
	}
	return ""
}

func MapFromStruct(value any) map[string]any {
	if value == nil {
		return map[string]any{}
	}
	raw, err := json.Marshal(value)
	if err != nil {
		return map[string]any{}
	}
	var out map[string]any
	if err := json.Unmarshal(raw, &out); err != nil {
		return map[string]any{}
	}
	return out
}
