package persistence

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/nexuscrm/kernel/pkg/schemacatalog"
)

type permissionStateDirectWriterContextKey struct{}

// PermissionStateDirectWriterCoordinator gives registered direct SQL writers
// one pessimistic transaction owner and the shared transaction-bound revision
// Authority. Individual writers still own their exact target locks and
// semantic before/after comparison.
type PermissionStateDirectWriterCoordinator struct {
	transactions                                *TransactionManager
	authority                                   *PermissionStateAuthority
	writerSurfaceIDs                            map[string]map[string]struct{}
	surfacesByTableName                         map[string]schemacatalog.PermissionStateMutationSurface
	permissionSetDefinitionRelationsByTableName map[string]schemacatalog.PermissionSetDefinitionRelation
}

// NewPermissionStateDirectWriterCoordinator binds direct writers to the exact
// TransactionManager already owned by the production composition.
func NewPermissionStateDirectWriterCoordinator(
	transactions *TransactionManager,
	authority *PermissionStateAuthority,
) (*PermissionStateDirectWriterCoordinator, error) {
	if transactions == nil {
		return nil, errors.New("permission state direct writer: transaction manager is required")
	}
	if authority == nil || authority.transactions != transactions {
		return nil, errors.New("permission state direct writer: shared authority is required")
	}
	inventory, err := schemacatalog.ManagedPermissionStateDirectWriterInventory()
	if err != nil {
		return nil, fmt.Errorf("permission state direct writer: load inventory: %w", err)
	}
	writerSurfaceIDs := make(map[string]map[string]struct{})
	for _, writer := range inventory.Writers() {
		surfaceIDs := make(map[string]struct{}, len(writer.SurfaceIDs))
		for _, surfaceID := range writer.SurfaceIDs {
			surfaceIDs[surfaceID] = struct{}{}
		}
		writerSurfaceIDs[writer.WriterID] = surfaceIDs
	}
	mutations, err := schemacatalog.ManagedPermissionStateMutationInventory()
	if err != nil {
		return nil, fmt.Errorf("permission state direct writer: load mutation surfaces: %w", err)
	}
	surfacesByTableName := make(map[string]schemacatalog.PermissionStateMutationSurface)
	for _, surface := range mutations.Surfaces() {
		surfacesByTableName[surface.TableName] = surface
	}
	definitions, err := schemacatalog.ManagedPermissionSetDefinitionInventory()
	if err != nil {
		return nil, fmt.Errorf("permission state direct writer: load Permission Set definition relations: %w", err)
	}
	permissionSetDefinitionRelationsByTableName := make(map[string]schemacatalog.PermissionSetDefinitionRelation)
	for _, relation := range definitions.Relations() {
		permissionSetDefinitionRelationsByTableName[strings.ToLower(relation.TableName)] = relation
	}
	return &PermissionStateDirectWriterCoordinator{
		transactions:        transactions,
		authority:           authority,
		writerSurfaceIDs:    writerSurfaceIDs,
		surfacesByTableName: surfacesByTableName,
		permissionSetDefinitionRelationsByTableName: permissionSetDefinitionRelationsByTableName,
	}, nil
}

func (coordinator *PermissionStateDirectWriterCoordinator) shouldRun(
	ctx context.Context,
	writerID string,
) bool {
	if coordinator == nil {
		return false
	}
	return !permissionStateDirectWriterIsActive(ctx, writerID)
}

func permissionStateDirectWriterIsActive(
	ctx context.Context,
	writerID string,
) bool {
	if ctx == nil {
		return false
	}
	active, _ := ctx.Value(permissionStateDirectWriterContextKey{}).(map[string]struct{})
	_, alreadyRunning := active[writerID]
	return alreadyRunning
}

func (coordinator *PermissionStateDirectWriterCoordinator) run(
	ctx context.Context,
	writerID string,
	mutation func(context.Context) (bool, error),
) error {
	if coordinator == nil || coordinator.transactions == nil || coordinator.authority == nil {
		return errors.New("permission state direct writer: coordinator is unavailable")
	}
	if ctx == nil {
		return errors.New("permission state direct writer: context is required")
	}
	if _, registered := coordinator.writerSurfaceIDs[writerID]; !registered {
		return fmt.Errorf("permission state direct writer: unregistered writer %q", writerID)
	}
	if mutation == nil {
		return fmt.Errorf("permission state direct writer: writer %s mutation is required", writerID)
	}
	return coordinator.transactions.WithPessimisticTransaction(ctx, func(txCtx context.Context) error {
		changed, err := mutation(withPermissionStateDirectWriter(txCtx, writerID))
		if err != nil {
			return err
		}
		return coordinator.authority.RequestAdvanceIfChanged(txCtx, changed)
	})
}

func withPermissionStateDirectWriter(ctx context.Context, writerID string) context.Context {
	active, _ := ctx.Value(permissionStateDirectWriterContextKey{}).(map[string]struct{})
	next := make(map[string]struct{}, len(active)+1)
	for activeWriterID := range active {
		next[activeWriterID] = struct{}{}
	}
	next[writerID] = struct{}{}
	return context.WithValue(ctx, permissionStateDirectWriterContextKey{}, next)
}
