package option

import (
	"context"

	"github.com/sagernet/sing-box/schema"
	E "github.com/sagernet/sing/common/exceptions"
	"github.com/sagernet/sing/common/json"
	"github.com/sagernet/sing/common/json/badjson"
	"github.com/sagernet/sing/service"
)

type CertificateProviderOptionsRegistry interface {
	OptionTypes() []string
	CreateOptions(providerType string) (any, bool)
}

type _CertificateProvider struct {
	Type    string `json:"type"`
	Tag     string `json:"tag,omitempty"`
	Options any    `json:"-"`
}

type CertificateProvider _CertificateProvider

func (h *CertificateProvider) MarshalJSONContext(ctx context.Context) ([]byte, error) {
	return badjson.MarshallObjectsContext(ctx, (*_CertificateProvider)(h), h.Options)
}

func (h *CertificateProvider) UnmarshalJSONContext(ctx context.Context, content []byte) error {
	err := json.UnmarshalContext(ctx, content, (*_CertificateProvider)(h))
	if err != nil {
		return err
	}
	registry := service.FromContext[CertificateProviderOptionsRegistry](ctx)
	if registry == nil {
		return E.New("missing certificate provider options registry in context")
	}
	options, loaded := registry.CreateOptions(h.Type)
	if !loaded {
		return E.New("unknown certificate provider type: ", h.Type)
	}
	err = badjson.UnmarshallExcludedContext(ctx, content, (*_CertificateProvider)(h), options)
	if err != nil {
		return err
	}
	h.Options = options
	return nil
}

func (h CertificateProvider) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
	return builder.Define("CertificateProvider", func() (*schema.Node, error) {
		registry := service.FromContext[CertificateProviderOptionsRegistry](builder.Context())
		if registry == nil {
			return nil, E.New("missing certificate provider options registry in context")
		}
		return registryUnion(builder, registry, nil, true)
	})
}

type CertificateProviderOptions struct {
	Tag     string `json:"-"`
	Type    string `json:"-"`
	Options any    `json:"-"`
}

type _CertificateProviderInline struct {
	Type string `json:"type"`
}

func (o *CertificateProviderOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
	if o.Tag != "" {
		return json.Marshal(o.Tag)
	}
	return badjson.MarshallObjectsContext(ctx, _CertificateProviderInline{Type: o.Type}, o.Options)
}

func (o *CertificateProviderOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
	if len(content) == 0 {
		return E.New("empty certificate_provider value")
	}
	if content[0] == '"' {
		return json.UnmarshalContext(ctx, content, &o.Tag)
	}
	var inline _CertificateProviderInline
	err := json.UnmarshalContext(ctx, content, &inline)
	if err != nil {
		return err
	}
	o.Type = inline.Type
	if o.Type == "" {
		return E.New("missing certificate provider type")
	}
	registry := service.FromContext[CertificateProviderOptionsRegistry](ctx)
	if registry == nil {
		return E.New("missing certificate provider options registry in context")
	}
	options, loaded := registry.CreateOptions(o.Type)
	if !loaded {
		return E.New("unknown certificate provider type: ", o.Type)
	}
	err = badjson.UnmarshallExcludedContext(ctx, content, &inline, options)
	if err != nil {
		return err
	}
	o.Options = options
	return nil
}

func (o *CertificateProviderOptions) IsShared() bool {
	return o.Tag != ""
}

func (o CertificateProviderOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
	return builder.Define("CertificateProviderReference", func() (*schema.Node, error) {
		registry := service.FromContext[CertificateProviderOptionsRegistry](builder.Context())
		if registry == nil {
			return nil, E.New("missing certificate provider options registry in context")
		}
		union, err := registryUnion(builder, registry, nil, false)
		if err != nil {
			return nil, err
		}
		return schema.AnyOf(schema.TagReferenceNode("certificate_provider"), union), nil
	})
}
