package query

import (
	"context"

	"github.com/zitadel/zitadel/internal/api/authz"
	"github.com/zitadel/zitadel/internal/eventstore"
	"github.com/zitadel/zitadel/internal/feature"
	feature_v1 "github.com/zitadel/zitadel/internal/repository/feature"
	"github.com/zitadel/zitadel/internal/repository/feature/feature_v2"
)

type InstanceFeaturesReadModel struct {
	*eventstore.ReadModel
	system   *SystemFeatures
	instance *InstanceFeatures
}

func NewInstanceFeaturesReadModel(ctx context.Context, system *SystemFeatures) *InstanceFeaturesReadModel {
	instanceID := authz.GetInstance(ctx).InstanceID()
	m := &InstanceFeaturesReadModel{
		ReadModel: &eventstore.ReadModel{
			AggregateID:   instanceID,
			ResourceOwner: instanceID,
		},
		instance: new(InstanceFeatures),
		system:   system,
	}
	m.populateFromSystem()
	return m
}

func (m *InstanceFeaturesReadModel) Reduce() (err error) {
	for _, event := range m.Events {
		switch e := event.(type) {
		case *feature_v2.ResetEvent:
			m.reduceReset()
		case *feature_v1.SetEvent[feature_v1.Boolean]:
			err = reduceInstanceFeatureSet(
				m.instance,
				feature_v1.DefaultLoginInstanceEventToV2(e),
			)
		case *feature_v2.SetEvent[bool]:
			err = reduceInstanceFeatureSet(m.instance, e)
		case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
			err = reduceInstanceFeatureSet(m.instance, e)
		}
		if err != nil {
			return err
		}
	}
	return m.ReadModel.Reduce()
}

func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
	return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
		AwaitOpenTransactions().
		AddQuery().
		AggregateTypes(feature_v2.AggregateType).
		EventTypes(
			feature_v1.DefaultLoginInstanceEventType,
			feature_v2.InstanceResetEventType,
			feature_v2.InstanceLoginDefaultOrgEventType,
			feature_v2.InstanceTriggerIntrospectionProjectionsEventType,
			feature_v2.InstanceLegacyIntrospectionEventType,
			feature_v2.InstanceUserSchemaEventType,
			feature_v2.InstanceTokenExchangeEventType,
			feature_v2.InstanceActionsEventType,
			feature_v2.InstanceImprovedPerformanceEventType,
		).
		Builder().ResourceOwner(m.ResourceOwner)
}

func (m *InstanceFeaturesReadModel) reduceReset() {
	if m.populateFromSystem() {
		return
	}
	m.instance = nil
	m.instance = new(InstanceFeatures)
}

func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
	if m.system == nil {
		return false
	}
	m.instance.LoginDefaultOrg = m.system.LoginDefaultOrg
	m.instance.TriggerIntrospectionProjections = m.system.TriggerIntrospectionProjections
	m.instance.LegacyIntrospection = m.system.LegacyIntrospection
	m.instance.UserSchema = m.system.UserSchema
	m.instance.TokenExchange = m.system.TokenExchange
	m.instance.Actions = m.system.Actions
	m.instance.ImprovedPerformance = m.system.ImprovedPerformance
	return true
}

func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_v2.SetEvent[T]) error {
	level, key, err := event.FeatureInfo()
	if err != nil {
		return err
	}
	switch key {
	case feature.KeyUnspecified:
		return nil
	case feature.KeyLoginDefaultOrg:
		features.LoginDefaultOrg.set(level, event.Value)
	case feature.KeyTriggerIntrospectionProjections:
		features.TriggerIntrospectionProjections.set(level, event.Value)
	case feature.KeyLegacyIntrospection:
		features.LegacyIntrospection.set(level, event.Value)
	case feature.KeyUserSchema:
		features.UserSchema.set(level, event.Value)
	case feature.KeyTokenExchange:
		features.TokenExchange.set(level, event.Value)
	case feature.KeyActions:
		features.Actions.set(level, event.Value)
	case feature.KeyImprovedPerformance:
		features.ImprovedPerformance.set(level, event.Value)
	}
	return nil
}