package runtimeassembly

import (
	"context"
	"errors"

	database "github.com/nexuscrm/db"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

type effectivePermissionReader interface {
	EffectivePermissions(
		context.Context,
		*models.UserSession,
		string,
	) (*contract.EffectivePermissionsResult, error)
}

type selectedEffectivePermissionReader interface {
	EffectivePermissionsSelected(
		context.Context,
		*models.UserSession,
		string,
		permissionsemantic.Selection,
	) (*contract.EffectivePermissionsResult, error)
}

type effectiveActionPermissionReader interface {
	LoadCurrentEffectivePermissionSnapshot(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error)
}

type permissionSnapshotTransactions interface {
	WithTransaction(context.Context, func(context.Context) error) error
	IsTransactionActive(context.Context) bool
}

type permissionStateSource interface {
	LoadCurrent(context.Context) (persistence.PermissionState, error)
	LockCurrent(context.Context) (persistence.PermissionState, error)
}

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

type effectiveFieldPermissionChecker interface {
	CheckFieldVisibilityWithUser(
		context.Context,
		string,
		string,
		*models.UserSession,
	) (bool, error)
}

// EffectivePermissionProjection keeps the existing PermissionAdmin as the
// sole computation/disclosure owner while binding its multi-query projection
// to one Permission State snapshot.
type EffectivePermissionProjection struct {
	reader       effectivePermissionReader
	actionReader effectiveActionPermissionReader
	transactions permissionSnapshotTransactions
	state        permissionStateSource
	objects      effectiveObjectPermissionChecker
	fields       effectiveFieldPermissionChecker
}

func NewEffectivePermissionProjection(
	connection *database.TiDBConnection,
	service *permissions.PermissionService,
) (*EffectivePermissionProjection, error) {
	if connection == nil || connection.DB() == nil || service == nil {
		return nil, errors.New(
			"effective permission projection requires Runtime database and permission service",
		)
	}
	state, err := persistence.NewPermissionStateRepository(connection.DB())
	if err != nil {
		return nil, err
	}
	return newEffectivePermissionProjection(
		permissions.NewContractPermissionAdmin(service, nil),
		persistence.NewTransactionManager(connection),
		state,
		service,
	)
}

func newEffectivePermissionProjection(
	reader effectivePermissionReader,
	transactions permissionSnapshotTransactions,
	state permissionStateSource,
	objects effectiveObjectPermissionChecker,
) (*EffectivePermissionProjection, error) {
	if reader == nil || transactions == nil || state == nil || objects == nil {
		return nil, errors.New("effective permission projection dependencies are required")
	}
	fields, ok := objects.(effectiveFieldPermissionChecker)
	if !ok {
		return nil, errors.New(
			"effective permission projection requires field visibility authority",
		)
	}
	return &EffectivePermissionProjection{
		reader: reader, actionReader: actionPermissionReaderFrom(objects),
		transactions: transactions, state: state,
		objects: objects, fields: fields,
	}, nil
}

func actionPermissionReaderFrom(value any) effectiveActionPermissionReader {
	reader, _ := value.(effectiveActionPermissionReader)
	return reader
}

// CheckObjectPermissionWithUser delegates object guards to the existing
// PermissionService decision owner. In particular, raw effective CRUD rows are
// not sufficient because metadata-required permissions and access policies are
// part of the authoritative object decision.
func (projection *EffectivePermissionProjection) CheckObjectPermissionWithUser(
	ctx context.Context,
	objectAPIName string,
	operation string,
	user *models.UserSession,
) bool {
	return projection != nil && projection.objects != nil && ctx != nil && user != nil &&
		projection.objects.CheckObjectPermissionWithUser(
			ctx, objectAPIName, operation, user,
		)
}

// CheckFieldVisibilityWithUser delegates preview disclosure to the existing
// PermissionService. Effective-permission rows are not a replacement for the
// field decision owner because metadata read-protection and policy participate
// in the final answer.
func (projection *EffectivePermissionProjection) CheckFieldVisibilityWithUser(
	ctx context.Context,
	objectAPIName string,
	fieldAPIName string,
	user *models.UserSession,
) (bool, error) {
	if projection == nil || projection.fields == nil || ctx == nil || user == nil {
		return false, nil
	}
	return projection.fields.CheckFieldVisibilityWithUser(
		ctx, objectAPIName, fieldAPIName, user,
	)
}

func (projection *EffectivePermissionProjection) EffectivePermissions(
	ctx context.Context,
	viewer *models.UserSession,
	targetPrincipalID string,
	floor permissionstatev1.Version,
) (*contract.EffectivePermissionsResult, error) {
	if projection == nil || projection.reader == nil ||
		projection.transactions == nil || projection.state == nil ||
		ctx == nil || viewer == nil || floor.Validate() != nil {
		return nil, &permissionsemantic.Failure{
			Category: permissionsemantic.FailureUnavailable,
		}
	}
	var result *contract.EffectivePermissionsResult
	err := projection.transactions.WithTransaction(ctx, func(txCtx context.Context) error {
		var readErr error
		result, readErr = projection.effectivePermissionsAtFloor(
			txCtx, viewer, targetPrincipalID, floor,
		)
		return readErr
	})
	if err != nil {
		return nil, err
	}
	if result == nil {
		return nil, &permissionsemantic.Failure{
			Category: permissionsemantic.FailureInternal,
		}
	}
	return result, nil
}

// EffectivePermissionsSelected binds a bounded section/resource projection to
// the same Permission State snapshot as the complete read. The selection is
// interpreted by the existing permission owner before repository work starts.
func (projection *EffectivePermissionProjection) EffectivePermissionsSelected(
	ctx context.Context,
	viewer *models.UserSession,
	targetPrincipalID string,
	selection permissionsemantic.Selection,
	floor permissionstatev1.Version,
) (*contract.EffectivePermissionsResult, error) {
	if projection == nil || projection.reader == nil ||
		projection.transactions == nil || projection.state == nil ||
		ctx == nil || viewer == nil || len(selection.Sections) == 0 ||
		floor.Validate() != nil {
		return nil, permissionProjectionFailure(permissionsemantic.FailureUnavailable)
	}
	var result *contract.EffectivePermissionsResult
	err := projection.transactions.WithTransaction(ctx, func(txCtx context.Context) error {
		var readErr error
		result, readErr = projection.EffectivePermissionsSelectedInTransaction(txCtx, viewer, targetPrincipalID, selection, floor)
		return readErr
	})
	if err != nil {
		return nil, err
	}
	if result == nil {
		return nil, permissionProjectionFailure(permissionsemantic.FailureInternal)
	}
	return result, nil
}

// EffectivePermissionsSelectedInTransaction shares the caller's metadata read
// snapshot. It neither opens a second transaction nor consults a serving cache.
func (projection *EffectivePermissionProjection) EffectivePermissionsSelectedInTransaction(
	ctx context.Context, viewer *models.UserSession, targetPrincipalID string,
	selection permissionsemantic.Selection, floor permissionstatev1.Version,
) (*contract.EffectivePermissionsResult, error) {
	if projection == nil || projection.transactions == nil || !projection.transactions.IsTransactionActive(ctx) || viewer == nil || len(selection.Sections) == 0 || floor.Validate() != nil {
		return nil, permissionProjectionFailure(permissionsemantic.FailureUnavailable)
	}
	reader, ok := projection.reader.(selectedEffectivePermissionReader)
	if !ok {
		return nil, permissionProjectionFailure(permissionsemantic.FailureUnavailable)
	}
	if err := projection.ValidatePermissionFloor(ctx, floor); err != nil {
		return nil, err
	}
	return reader.EffectivePermissionsSelected(ctx, viewer, targetPrincipalID, selection)
}

// EffectiveActionPermissions binds the small system/custom permission set used
// by Catalog action preparation to one Permission State snapshot. It avoids the
// complete object/field/App/navigation/record-type export, while object guards
// continue to call the authoritative PermissionService decision owner.
func (projection *EffectivePermissionProjection) EffectiveActionPermissions(
	ctx context.Context,
	viewer *models.UserSession,
	floor permissionstatev1.Version,
) (*contract.EffectiveActionPermissions, error) {
	if err := projection.validateActionPermissionRequest(ctx, viewer, floor, false); err != nil {
		return nil, err
	}
	var result *contract.EffectiveActionPermissions
	err := projection.transactions.WithTransaction(ctx, func(txCtx context.Context) error {
		var readErr error
		result, readErr = projection.effectiveActionPermissionsAtFloor(
			txCtx, viewer, floor,
		)
		return readErr
	})
	if err != nil {
		return nil, err
	}
	if result == nil {
		return nil, permissionProjectionFailure(permissionsemantic.FailureInternal)
	}
	return result, nil
}

// EffectivePermissionsWithinTransaction binds the permission projection to an
// already-owned Runtime transaction. It is used when authorization must remain
// valid through the business effect and terminal receipt commit; opening a
// nested snapshot here would leave a revocation race between the two commits.
func (projection *EffectivePermissionProjection) EffectivePermissionsWithinTransaction(
	ctx context.Context,
	viewer *models.UserSession,
	targetPrincipalID string,
	floor permissionstatev1.Version,
) (*contract.EffectivePermissionsResult, error) {
	if projection == nil || projection.reader == nil ||
		projection.transactions == nil || projection.state == nil ||
		ctx == nil || viewer == nil || floor.Validate() != nil {
		return nil, &permissionsemantic.Failure{
			Category: permissionsemantic.FailureUnavailable,
		}
	}
	if !projection.transactions.IsTransactionActive(ctx) {
		return nil, &permissionsemantic.Failure{
			Category: permissionsemantic.FailureInternal,
		}
	}
	return projection.effectivePermissionsAtFloor(
		ctx, viewer, targetPrincipalID, floor,
	)
}

// ValidatePermissionFloor proves that one previously materialized permission
// projection is still bound to the exact current Permission State. Serving
// caches must call this on every hit; the cache key alone is not authority and
// a newer or incomparable state must fail closed.
func (projection *EffectivePermissionProjection) ValidatePermissionFloor(
	ctx context.Context,
	floor permissionstatev1.Version,
) error {
	if projection == nil || projection.state == nil || ctx == nil || floor.Validate() != nil {
		return permissionProjectionFailure(permissionsemantic.FailureUnavailable)
	}
	current, err := projection.state.LoadCurrent(ctx)
	if err != nil {
		return err
	}
	comparison, err := current.Version.Compare(floor)
	if err != nil || comparison != 0 {
		return permissionProjectionFailure(permissionsemantic.FailureUnavailable)
	}
	return nil
}

// EffectiveActionPermissionsWithinTransaction re-evaluates the same narrow
// action facts inside the business transaction. The final permission-floor
// fence remains mandatory and serializes the authorized effect with revocation.
func (projection *EffectivePermissionProjection) EffectiveActionPermissionsWithinTransaction(
	ctx context.Context,
	viewer *models.UserSession,
	floor permissionstatev1.Version,
) (*contract.EffectiveActionPermissions, error) {
	if err := projection.validateActionPermissionRequest(ctx, viewer, floor, true); err != nil {
		return nil, err
	}
	return projection.effectiveActionPermissionsAtFloor(ctx, viewer, floor)
}

// FencePermissionFloorWithinTransaction serializes the authorized effect with
// permission mutations at commit. A locking read observes a revocation that
// committed after the transaction snapshot was established; a later writer
// must wait until this effect commits.
func (projection *EffectivePermissionProjection) FencePermissionFloorWithinTransaction(
	ctx context.Context,
	floor permissionstatev1.Version,
) error {
	if projection == nil || projection.transactions == nil || projection.state == nil ||
		ctx == nil || floor.Validate() != nil ||
		!projection.transactions.IsTransactionActive(ctx) {
		return &permissionsemantic.Failure{
			Category: permissionsemantic.FailureInternal,
		}
	}
	current, err := projection.state.LockCurrent(ctx)
	if err != nil {
		return err
	}
	comparison, err := current.Version.Compare(floor)
	if err != nil || comparison != 0 {
		return &permissionsemantic.Failure{
			Category: permissionsemantic.FailureUnavailable,
		}
	}
	return nil
}

func (projection *EffectivePermissionProjection) effectivePermissionsAtFloor(
	ctx context.Context,
	viewer *models.UserSession,
	targetPrincipalID string,
	floor permissionstatev1.Version,
) (*contract.EffectivePermissionsResult, error) {
	if err := projection.ValidatePermissionFloor(ctx, floor); err != nil {
		// Permission State is intentionally not a caller-selected stale axis.
		// A retry obtains a fresh admitted floor; no internal lineage or revision
		// is disclosed over the Platform contract.
		return nil, err
	}
	result, err := projection.reader.EffectivePermissions(
		ctx, viewer, targetPrincipalID,
	)
	if err != nil {
		return nil, err
	}
	if result == nil {
		return nil, &permissionsemantic.Failure{
			Category: permissionsemantic.FailureInternal,
		}
	}
	return result, nil
}

func (projection *EffectivePermissionProjection) validateActionPermissionRequest(
	ctx context.Context,
	viewer *models.UserSession,
	floor permissionstatev1.Version,
	requireOwnedTransaction bool,
) error {
	if projection == nil || projection.actionReader == nil ||
		projection.transactions == nil || projection.state == nil ||
		ctx == nil || viewer == nil || viewer.ID == "" || floor.Validate() != nil {
		return permissionProjectionFailure(permissionsemantic.FailureUnavailable)
	}
	if requireOwnedTransaction && !projection.transactions.IsTransactionActive(ctx) {
		return permissionProjectionFailure(permissionsemantic.FailureInternal)
	}
	return nil
}

func (projection *EffectivePermissionProjection) effectiveActionPermissionsAtFloor(
	ctx context.Context,
	viewer *models.UserSession,
	floor permissionstatev1.Version,
) (*contract.EffectiveActionPermissions, error) {
	subject, err := permissionstatev1.NewSubject(viewer.ID, viewer.ProfileID)
	if err != nil {
		return nil, permissionProjectionFailure(permissionsemantic.FailureUnavailable)
	}
	snapshot, err := projection.actionReader.LoadCurrentEffectivePermissionSnapshot(
		ctx,
		subject,
	)
	if err != nil {
		return nil, err
	}
	comparison, err := snapshot.Version().Compare(floor)
	if err != nil || comparison != 0 {
		return nil, permissionProjectionFailure(permissionsemantic.FailureUnavailable)
	}
	return &contract.EffectiveActionPermissions{
		SystemPermissions: permissionFactMap(snapshot.SystemPermissions()),
		CustomPermissions: permissionFactMap(snapshot.CustomPermissions()),
	}, nil
}

func permissionFactMap(values []string) map[string]bool {
	result := make(map[string]bool, len(values))
	for _, value := range values {
		result[value] = true
	}
	return result
}

func permissionProjectionFailure(
	category permissionsemantic.FailureCategory,
) *permissionsemantic.Failure {
	return &permissionsemantic.Failure{Category: category}
}
