package plugins

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"time"
)

// LicenseChecker is a minimal interface satisfied by *license.Evaluator.
// Defined here to avoid an import cycle (plugins → license).
type LicenseChecker interface {
	CheckFeature(feature string) error
}

// FeaturePlugins is the license feature string that gates all plugin calls.
// Must match license.FeaturePlugins ("plugins") — verified by TestFeaturePluginsConstantMatch.
const FeaturePlugins = "plugins"

// ErrLicenseRequired is returned by Service.Call when a valid commercial
// license is required but not present.
var ErrLicenseRequired = errors.New("commercial license required for plugin execution")

type Service struct {
	registry Registry
	caller   Caller
	logger   *slog.Logger
	license  LicenseChecker
}

func NewService(roots []string, timeout time.Duration, logger *slog.Logger) *Service {
	registry := NewDirectoryRegistry(roots)
	return &Service{
		registry: registry,
		caller:   NewProcessCaller(timeout, logger),
		logger:   logger,
	}
}

func NewServiceWith(registry Registry, caller Caller, logger *slog.Logger) *Service {
	return &Service{registry: registry, caller: caller, logger: logger}
}

// WithLicense attaches a license checker. All plugin calls will be gated.
func (s *Service) WithLicense(lc LicenseChecker) *Service {
	if s != nil {
		s.license = lc
	}
	return s
}

func (s *Service) Refresh(ctx context.Context) error {
	if s == nil || s.registry == nil {
		return ErrNotFound
	}
	return s.registry.Refresh(ctx)
}

func (s *Service) List(ctx context.Context, capability string) ([]Manifest, error) {
	if s == nil || s.registry == nil {
		return nil, ErrNotFound
	}
	return s.registry.List(ctx, capability)
}

func (s *Service) ListAll(ctx context.Context, capability string) ([]Manifest, error) {
	if s == nil || s.registry == nil {
		return nil, ErrNotFound
	}
	return s.registry.ListAll(ctx, capability)
}

func (s *Service) Manifest(ctx context.Context, pluginID string) (Manifest, error) {
	if s == nil || s.registry == nil {
		return Manifest{}, ErrNotFound
	}
	return s.registry.Get(ctx, pluginID)
}

func (s *Service) Config(ctx context.Context, pluginID string) (map[string]any, error) {
	manifest, err := s.Manifest(ctx, pluginID)
	if err != nil {
		return nil, err
	}
	raw, err := os.ReadFile(filepath.Join(manifest.Dir, "plugin.config.json"))
	if err != nil {
		if os.IsNotExist(err) {
			return map[string]any{}, nil
		}
		return nil, err
	}
	var config map[string]any
	if err := json.Unmarshal(raw, &config); err != nil {
		return nil, err
	}
	if config == nil {
		config = map[string]any{}
	}
	return config, nil
}

func (s *Service) SetConfig(ctx context.Context, pluginID string, config map[string]any) (map[string]any, error) {
	manifest, err := s.Manifest(ctx, pluginID)
	if err != nil {
		return nil, err
	}
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	if config == nil {
		config = map[string]any{}
	}
	raw, err := json.MarshalIndent(config, "", "  ")
	if err != nil {
		return nil, err
	}
	if err := os.WriteFile(filepath.Join(manifest.Dir, "plugin.config.json"), raw, 0o600); err != nil {
		return nil, err
	}
	return config, nil
}

func (s *Service) MergeConfig(ctx context.Context, pluginID string, base map[string]any) map[string]any {
	out := map[string]any{}
	for key, value := range base {
		out[key] = value
	}
	config, err := s.Config(ctx, pluginID)
	if err != nil {
		return out
	}
	for key, value := range config {
		out[key] = value
	}
	return out
}

func (s *Service) Call(ctx context.Context, pluginID string, capability string, method string, params any, result any) error {
	if s == nil || s.registry == nil || s.caller == nil {
		return ErrNotFound
	}
	if s.license != nil {
		if err := s.license.CheckFeature(FeaturePlugins); err != nil {
			return fmt.Errorf("%w: %w", ErrLicenseRequired, err)
		}
	}
	manifest, err := s.registry.Resolve(ctx, pluginID, capability)
	if err != nil {
		return err
	}
	return s.caller.Call(ctx, manifest, method, params, result)
}

type SMSSendRequest struct {
	Phone        string         `json:"phone"`
	Code         string         `json:"code,omitempty"`
	Purpose      string         `json:"purpose,omitempty"`
	TemplateCode string         `json:"template_code,omitempty"`
	TemplateData map[string]any `json:"template_data,omitempty"`
	Config       map[string]any `json:"config,omitempty"`
}

type SMSSendResponse struct {
	OK        bool           `json:"ok"`
	Message   string         `json:"message,omitempty"`
	Provider  string         `json:"provider,omitempty"`
	MessageID string         `json:"message_id,omitempty"`
	Raw       map[string]any `json:"raw,omitempty"`
}

func (s *Service) SendSMS(ctx context.Context, pluginID string, req SMSSendRequest) (SMSSendResponse, error) {
	var resp SMSSendResponse
	err := s.Call(ctx, pluginID, CapabilitySMS, MethodSMSSend, req, &resp)
	return resp, err
}

type EmailSendRequest struct {
	To           string         `json:"to"`
	Subject      string         `json:"subject"`
	TextBody     string         `json:"text_body,omitempty"`
	HTMLBody     string         `json:"html_body,omitempty"`
	Template     string         `json:"template,omitempty"`
	TemplateData map[string]any `json:"template_data,omitempty"`
	Config       map[string]any `json:"config,omitempty"`
}

type EmailSendResponse struct {
	OK        bool           `json:"ok"`
	Message   string         `json:"message,omitempty"`
	Provider  string         `json:"provider,omitempty"`
	MessageID string         `json:"message_id,omitempty"`
	Raw       map[string]any `json:"raw,omitempty"`
}

func (s *Service) SendEmail(ctx context.Context, pluginID string, req EmailSendRequest) (EmailSendResponse, error) {
	var resp EmailSendResponse
	err := s.Call(ctx, pluginID, CapabilityEmail, MethodEmailSend, req, &resp)
	return resp, err
}

type VerificationRequest struct {
	Mode        string         `json:"mode"`
	UserID      int64          `json:"user_id,omitempty"`
	RealName    string         `json:"real_name,omitempty"`
	IDCardNo    string         `json:"id_card_no,omitempty"`
	Phone       string         `json:"phone,omitempty"`
	ImageBase64 string         `json:"image_base64,omitempty"`
	ReturnURL   string         `json:"return_url,omitempty"`
	NotifyURL   string         `json:"notify_url,omitempty"`
	ExternalID  string         `json:"external_id,omitempty"`
	RecordID    int64          `json:"record_id,omitempty"`
	Payload     map[string]any `json:"payload,omitempty"`
	Config      map[string]any `json:"config,omitempty"`
}

type VerificationResponse struct {
	Passed     bool           `json:"passed"`
	Status     string         `json:"status,omitempty"`
	Reason     string         `json:"reason,omitempty"`
	Provider   string         `json:"provider,omitempty"`
	Mode       string         `json:"mode,omitempty"`
	ExternalID string         `json:"external_id,omitempty"`
	URL        string         `json:"url,omitempty"`
	QRPayload  string         `json:"qr_payload,omitempty"`
	ExpiresIn  int            `json:"expires_in,omitempty"`
	Score      *float64       `json:"score,omitempty"`
	Raw        map[string]any `json:"raw,omitempty"`
}

func (s *Service) VerifyIdentity(ctx context.Context, pluginID string, req VerificationRequest) (VerificationResponse, error) {
	var resp VerificationResponse
	err := s.Call(ctx, pluginID, CapabilityVerification, MethodVerificationVerify, req, &resp)
	return resp, err
}

func (s *Service) StartVerificationSession(ctx context.Context, pluginID string, req VerificationRequest) (VerificationResponse, error) {
	var resp VerificationResponse
	err := s.Call(ctx, pluginID, CapabilityVerification, MethodVerificationStartSession, req, &resp)
	return resp, err
}

func (s *Service) QueryVerificationResult(ctx context.Context, pluginID string, req VerificationRequest) (VerificationResponse, error) {
	var resp VerificationResponse
	err := s.Call(ctx, pluginID, CapabilityVerification, MethodVerificationQueryResult, req, &resp)
	return resp, err
}

func (s *Service) HandleVerificationNotify(ctx context.Context, pluginID string, req VerificationRequest) (VerificationResponse, error) {
	var resp VerificationResponse
	err := s.Call(ctx, pluginID, CapabilityVerification, MethodVerificationHandleNotify, req, &resp)
	return resp, err
}

type ProvisionRequest struct {
	PluginID            string         `json:"plugin_id"`
	Instance            map[string]any `json:"instance,omitempty"`
	Product             map[string]any `json:"product,omitempty"`
	Order               map[string]any `json:"order,omitempty"`
	User                map[string]any `json:"user,omitempty"`
	Snapshot            map[string]any `json:"snapshot,omitempty"`
	UpstreamParams      map[string]any `json:"upstream_params,omitempty"`
	InterfaceConfig     map[string]any `json:"interface_config,omitempty"`
	PluginFields        map[string]any `json:"plugin_fields,omitempty"`
	ProductConfigValues []any          `json:"product_config_values,omitempty"`
	Action              string         `json:"action,omitempty"`
	ActionPayload       map[string]any `json:"action_payload,omitempty"`
	Config              map[string]any `json:"config,omitempty"`
	BackwardCompatible  map[string]any `json:"context,omitempty"`
}

type ProvisionResponse struct {
	OK        bool           `json:"ok"`
	Message   string         `json:"message,omitempty"`
	Retryable bool           `json:"retryable"`
	Result    map[string]any `json:"result,omitempty"`
}

func (s *Service) Provision(ctx context.Context, pluginID string, req ProvisionRequest) (ProvisionResponse, error) {
	var resp ProvisionResponse
	err := s.Call(ctx, pluginID, CapabilityProvision, MethodProvisionCreate, req, &resp)
	return resp, err
}

func (s *Service) ManageProvision(ctx context.Context, pluginID string, req ProvisionRequest) (ProvisionResponse, error) {
	var resp ProvisionResponse
	err := s.Call(ctx, pluginID, CapabilityProvision, MethodProvisionManage, req, &resp)
	return resp, err
}

type PaymentCreateRequest struct {
	OutTradeNo  string         `json:"out_trade_no"`
	AmountCents int            `json:"amount_cents"`
	Amount      string         `json:"amount"`
	Subject     string         `json:"subject"`
	Method      string         `json:"method,omitempty"`
	ClientIP    string         `json:"client_ip,omitempty"`
	NotifyURL   string         `json:"notify_url,omitempty"`
	ReturnURL   string         `json:"return_url,omitempty"`
	UserID      int64          `json:"user_id,omitempty"`
	Metadata    map[string]any `json:"metadata,omitempty"`
	Config      map[string]any `json:"config,omitempty"`
}

type PaymentCreateResponse struct {
	OK        bool           `json:"ok"`
	Message   string         `json:"message,omitempty"`
	TradeNo   string         `json:"trade_no,omitempty"`
	PayURL    string         `json:"payurl,omitempty"`
	QRCode    string         `json:"qrcode,omitempty"`
	URLScheme string         `json:"urlscheme,omitempty"`
	Raw       map[string]any `json:"raw,omitempty"`
}

func (s *Service) CreatePayment(ctx context.Context, pluginID string, req PaymentCreateRequest) (PaymentCreateResponse, error) {
	var resp PaymentCreateResponse
	err := s.Call(ctx, pluginID, CapabilityPayment, MethodPaymentCreate, req, &resp)
	return resp, err
}

type PaymentNotifyRequest struct {
	Method  string            `json:"method,omitempty"`
	Headers map[string]string `json:"headers,omitempty"`
	Params  map[string]string `json:"params,omitempty"`
	Body    string            `json:"body,omitempty"`
	Config  map[string]any    `json:"config,omitempty"`
}

type PaymentNotifyResponse struct {
	OK          bool           `json:"ok"`
	Paid        bool           `json:"paid"`
	Ignored     bool           `json:"ignored"`
	OutTradeNo  string         `json:"out_trade_no,omitempty"`
	TradeNo     string         `json:"trade_no,omitempty"`
	AmountCents int            `json:"amount_cents,omitempty"`
	Message     string         `json:"message,omitempty"`
	Raw         map[string]any `json:"raw,omitempty"`
}

func (s *Service) HandlePaymentNotify(ctx context.Context, pluginID string, req PaymentNotifyRequest) (PaymentNotifyResponse, error) {
	var resp PaymentNotifyResponse
	err := s.Call(ctx, pluginID, CapabilityPayment, MethodPaymentNotify, req, &resp)
	return resp, err
}
