package programchecker

import (
	"context"
	"errors"

	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	connectorcontract "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
)

var ErrReaderUnavailable = errors.New("programchecker: reader unavailable")

// ProgramMetadataReader is the checker's only object, field, relationship, and
// effective-schema read path. The production implementation is backed by
// MetadataService's effective snapshot.
type ProgramMetadataReader interface {
	Object(ctx context.Context, apiName string) (*models.ObjectMetadata, bool, error)
	Field(ctx context.Context, objectAPIName, fieldAPIName string) (*models.FieldMetadata, bool, error)
	Schemas(ctx context.Context) ([]*models.ObjectMetadata, error)
	ForeignKeyForRelationship(ctx context.Context, objectAPIName, relationshipName string) (string, bool, error)
	TargetObjectsForForeignKey(ctx context.Context, objectAPIName, foreignKey string) ([]string, bool, error)
}

type FormulaSignature struct {
	Spec       FormulaFunctionSpec
	Definition *FormulaFunctionDefinition
}

type FormulaCatalogReader interface {
	FormulaFunction(name string) (FormulaSignature, bool, error)
}

type ActionSignature struct {
	Name                    string
	Inputs                  []sharedcontract.StandardActionSchemaField
	Outputs                 []sharedcontract.StandardActionSchemaField
	SkipParameterValidation bool
}

func (s ActionSignature) clone() ActionSignature {
	s.Inputs = cloneActionSchemaFields(s.Inputs)
	s.Outputs = cloneActionSchemaFields(s.Outputs)
	return s
}

type ActionSignatureReader interface {
	ActionSignature(ctx context.Context, actionName string) (ActionSignature, bool, error)
}

type ActionSignatureReaderFunc func(ctx context.Context, actionName string) (ActionSignature, bool, error)

func (f ActionSignatureReaderFunc) ActionSignature(ctx context.Context, actionName string) (ActionSignature, bool, error) {
	if f == nil {
		return ActionSignature{}, false, ErrReaderUnavailable
	}
	sig, ok, err := f(ctx, actionName)
	if err != nil || !ok {
		return ActionSignature{}, ok, err
	}
	return sig.clone(), true, nil
}

type ConnectorMappingReader interface {
	ConnectorMappings(ctx context.Context, q connectorcontract.ResolveQuery) ([]connectorcontract.ConnectorFieldMappingInput, error)
}

type ConnectorMappingReaderFunc func(ctx context.Context, q connectorcontract.ResolveQuery) ([]connectorcontract.ConnectorFieldMappingInput, error)

func (f ConnectorMappingReaderFunc) ConnectorMappings(ctx context.Context, q connectorcontract.ResolveQuery) ([]connectorcontract.ConnectorFieldMappingInput, error) {
	if f == nil {
		return nil, ErrReaderUnavailable
	}
	rows, err := f(ctx, q)
	if err != nil {
		return nil, err
	}
	return cloneConnectorMappings(rows), nil
}

type ValidationRuleReader interface {
	ValidationRules(ctx context.Context, objectAPIName string) ([]*models.ValidationRule, error)
}

type ValidationRuleReaderFunc func(ctx context.Context, objectAPIName string) ([]*models.ValidationRule, error)

func (f ValidationRuleReaderFunc) ValidationRules(ctx context.Context, objectAPIName string) ([]*models.ValidationRule, error) {
	if f == nil {
		return nil, ErrReaderUnavailable
	}
	rules, err := f(ctx, objectAPIName)
	if err != nil {
		return nil, err
	}
	return cloneValidationRules(rules), nil
}

type CapabilityCatalogReader interface {
	Capability(ctx context.Context, apiName string) (bool, error)
}

type CapabilityCatalogReaderFunc func(ctx context.Context, apiName string) (bool, error)

func (f CapabilityCatalogReaderFunc) Capability(ctx context.Context, apiName string) (bool, error) {
	if f == nil {
		return false, ErrReaderUnavailable
	}
	return f(ctx, apiName)
}

type PolicyReader interface {
	Access(ctx context.Context, user *models.UserSession, objectName string, req sharedcontract.AccessRequest) (*sharedcontract.EffectiveObjectAccess, error)
	HasCapability(ctx context.Context, user *models.UserSession, capability sharedcontract.Capability) bool
}

type CapabilityPolicyAdapter struct {
	Policy sharedcontract.CapabilityPolicy
}

func (a CapabilityPolicyAdapter) Access(ctx context.Context, user *models.UserSession, objectName string, req sharedcontract.AccessRequest) (*sharedcontract.EffectiveObjectAccess, error) {
	if a.Policy == nil {
		return nil, ErrReaderUnavailable
	}
	return a.Policy.Access(ctx, user, objectName, req)
}

func (a CapabilityPolicyAdapter) HasCapability(ctx context.Context, user *models.UserSession, capability sharedcontract.Capability) bool {
	if a.Policy == nil {
		return false
	}
	return a.Policy.HasCapability(ctx, user, capability)
}

func cloneActionSchemaFields(in []sharedcontract.StandardActionSchemaField) []sharedcontract.StandardActionSchemaField {
	if in == nil {
		return nil
	}
	out := make([]sharedcontract.StandardActionSchemaField, len(in))
	copy(out, in)
	return out
}

func cloneConnectorMappings(in []connectorcontract.ConnectorFieldMappingInput) []connectorcontract.ConnectorFieldMappingInput {
	if in == nil {
		return nil
	}
	out := make([]connectorcontract.ConnectorFieldMappingInput, len(in))
	copy(out, in)
	return out
}

func cloneValidationRules(in []*models.ValidationRule) []*models.ValidationRule {
	if in == nil {
		return nil
	}
	out := make([]*models.ValidationRule, 0, len(in))
	for _, rule := range in {
		if rule == nil {
			out = append(out, nil)
			continue
		}
		clone := *rule
		out = append(out, &clone)
	}
	return out
}
