package runtimeassembly

import (
	"context"
	"errors"
	"strings"

	database "github.com/nexuscrm/db"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
	searchsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/searchv1"
)

type searchScopeOrderSource interface {
	GetScopeOrder(context.Context, string) ([]ports.MRUObjectInteraction, error)
}

type searchScopeReadAuthorizer interface {
	CheckObjectPermissionWithUser(
		context.Context,
		string,
		string,
		*models.UserSession,
	) bool
}

// SearchScopeOrderProjection binds effective object-read access and viewer MRU
// ordering to one Runtime-owned Permission State snapshot. Catalog eligibility
// remains request-bound and is intersected by the serving adapter.
type SearchScopeOrderProjection struct {
	authorization searchScopeReadAuthorizer
	interactions  searchScopeOrderSource
	transactions  permissionSnapshotTransactions
	state         permissionStateSource
}

func NewSearchScopeOrderProjection(
	connection *database.TiDBConnection,
	service *permissions.PermissionService,
) (*SearchScopeOrderProjection, error) {
	if connection == nil || connection.DB() == nil || service == nil {
		return nil, errors.New(
			"search scope-order projection requires Runtime database and permission service",
		)
	}
	state, err := persistence.NewPermissionStateRepository(connection.DB())
	if err != nil {
		return nil, err
	}
	return newSearchScopeOrderProjection(
		service,
		persistence.NewMRURepository(connection.DB()),
		persistence.NewTransactionManager(connection),
		state,
	)
}

func newSearchScopeOrderProjection(
	authorization searchScopeReadAuthorizer,
	interactions searchScopeOrderSource,
	transactions permissionSnapshotTransactions,
	state permissionStateSource,
) (*SearchScopeOrderProjection, error) {
	if authorization == nil || interactions == nil ||
		transactions == nil || state == nil {
		return nil, errors.New("search scope-order projection dependencies are required")
	}
	return &SearchScopeOrderProjection{
		authorization: authorization, interactions: interactions,
		transactions: transactions, state: state,
	}, nil
}

func (projection *SearchScopeOrderProjection) ScopeOrder(
	ctx context.Context,
	viewer *models.UserSession,
	candidates []string,
	floor permissionstatev1.Version,
) ([]string, error) {
	if projection == nil || projection.authorization == nil ||
		projection.interactions == nil || projection.transactions == nil ||
		projection.state == nil || ctx == nil || viewer == nil ||
		viewer.ID == "" || floor.Validate() != nil {
		return nil, &searchsemantic.Failure{
			Category: commonsemantic.FailureUnavailable,
		}
	}

	var authorized []string
	var interactions []ports.MRUObjectInteraction
	err := projection.transactions.WithTransaction(ctx, func(txCtx context.Context) error {
		current, err := projection.state.LoadCurrent(txCtx)
		if err != nil {
			return err
		}
		comparison, err := current.Version.Compare(floor)
		if err != nil || comparison != 0 {
			return &searchsemantic.Failure{
				Category: commonsemantic.FailureUnavailable,
			}
		}
		authorized = make([]string, 0, len(candidates))
		for _, candidate := range candidates {
			if err := txCtx.Err(); err != nil {
				return err
			}
			if projection.authorization.CheckObjectPermissionWithUser(
				txCtx, candidate, constants.PermRead, viewer,
			) {
				authorized = append(authorized, candidate)
			}
		}
		interactions, err = projection.interactions.GetScopeOrder(txCtx, viewer.ID)
		return err
	})
	if err != nil {
		return nil, err
	}
	return orderAuthorizedSearchScope(authorized, interactions)
}

func orderAuthorizedSearchScope(
	authorizedCandidates []string,
	interactions []ports.MRUObjectInteraction,
) ([]string, error) {
	authorized := make(map[string]string, len(authorizedCandidates))
	for _, candidate := range authorizedCandidates {
		key := strings.ToLower(candidate)
		if key == "" {
			return nil, &searchsemantic.Failure{
				Category: commonsemantic.FailureInternal,
			}
		}
		if _, duplicate := authorized[key]; duplicate {
			return nil, &searchsemantic.Failure{
				Category: commonsemantic.FailureInternal,
			}
		}
		authorized[key] = candidate
	}

	ordered := make([]string, 0, len(authorized))
	emitted := make(map[string]struct{}, len(authorized))
	for _, interaction := range interactions {
		key := strings.ToLower(interaction.ObjectAPIName)
		canonical, allowed := authorized[key]
		if !allowed {
			continue
		}
		if _, duplicate := emitted[key]; duplicate {
			continue
		}
		emitted[key] = struct{}{}
		ordered = append(ordered, canonical)
	}
	for _, candidate := range authorizedCandidates {
		key := strings.ToLower(candidate)
		canonical, allowed := authorized[key]
		if !allowed {
			continue
		}
		if _, duplicate := emitted[key]; duplicate {
			continue
		}
		emitted[key] = struct{}{}
		ordered = append(ordered, canonical)
	}
	return ordered, nil
}
