package runtimeassembly

import (
	"context"
	"errors"
	"strings"

	database "github.com/nexuscrm/db"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	effective "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

// NavigationCaller is constructed only from authenticated Runtime admission.
// OwnerID is deployment-owned; none of these fields is accepted in an RPC body.
type NavigationCaller struct {
	OwnerID         string
	Actor           models.UserSession
	PermissionFloor permissionstatev1.Version
}

type navigationPermissions interface {
	EffectivePermissionsSelectedInTransaction(context.Context, *models.UserSession, string, effective.Selection, permissionstatev1.Version) (*contract.EffectivePermissionsResult, error)
}
type navigationSchemaReader interface {
	GetSchemaByAPIName(context.Context, string) (*models.ObjectMetadata, error)
}
type navigationAuthorizer interface {
	AuthorizeSystemPermissionAuthoring(context.Context, permissionstatev1.Version, *models.UserSession, string) (*permissions.PermissionAuthoringAuthorization, bool, error)
}

// ApplicationNavigationRuntime reads live application configuration. Its graph
// has no Catalog publisher, worker, permission writer or process-local cache.
type ApplicationNavigationRuntime struct {
	transactions *persistence.TransactionManager
	repository   *persistence.ApplicationNavigationRepository
	permissions  navigationPermissions
	authorizer   navigationAuthorizer
	schemas      navigationSchemaReader
}

func NewApplicationNavigationRuntime(connection *database.TiDBConnection, permissionService *permissions.PermissionService) (*ApplicationNavigationRuntime, error) {
	if connection == nil || connection.DB() == nil || permissionService == nil {
		return nil, errors.New("navigation requires database and permission service")
	}
	transactions := persistence.NewTransactionManager(connection)
	state, err := persistence.NewPermissionStateAuthority(transactions)
	if err != nil {
		return nil, err
	}
	authorizer, err := permissions.NewTransactionCurrentPermissionAuthoringPolicy(permissionService, state)
	if err != nil {
		return nil, err
	}
	projection, err := NewEffectivePermissionProjection(connection, permissionService)
	if err != nil {
		return nil, err
	}
	return &ApplicationNavigationRuntime{transactions: transactions, repository: persistence.NewApplicationNavigationRepository(connection.DB(), transactions), permissions: projection, authorizer: authorizer, schemas: persistence.NewMetadataRepository(connection.DB())}, nil
}
func validNavigationCaller(c NavigationCaller) bool {
	return semantic.ValidIdentifier(c.OwnerID) && c.Actor.ID != "" && c.PermissionFloor.Validate() == nil
}
func navigationFailure(category common.FailureCategory) error {
	return &common.Failure{Category: category}
}
func (r *ApplicationNavigationRuntime) Read(ctx context.Context, caller NavigationCaller, request semantic.ReadRequest) (semantic.Document, error) {
	if err := semantic.ValidateReadRequest(ctx, request); err != nil {
		return semantic.Document{}, err
	}
	if !validNavigationCaller(caller) {
		return semantic.Document{}, navigationFailure(common.FailureUnauthenticated)
	}
	var document semantic.Document
	err := r.transactions.WithTransaction(ctx, func(txCtx context.Context) error {
		authority, err := r.permissions.EffectivePermissionsSelectedInTransaction(txCtx, &caller.Actor, "", effective.Selection{Sections: []effective.Section{effective.SectionSystemPermissions}}, caller.PermissionFloor)
		if err != nil {
			return err
		}
		if authority == nil || !authority.SystemPermissions[string(constants.PermCustomizeApplication)] {
			return navigationFailure(common.FailureForbidden)
		}
		document, err = r.repository.Read(txCtx, caller.OwnerID, request.ApplicationID)
		return err
	})
	return document, err
}
func (r *ApplicationNavigationRuntime) Save(ctx context.Context, caller NavigationCaller, request semantic.SaveRequest) (persistence.NavigationMutationResult, error) {
	if err := semantic.ValidateSaveRequest(ctx, request); err != nil {
		return persistence.NavigationMutationResult{}, err
	}
	if !validNavigationCaller(caller) {
		return persistence.NavigationMutationResult{}, navigationFailure(common.FailureUnauthenticated)
	}
	var result persistence.NavigationMutationResult
	err := r.transactions.WithPessimisticTransaction(ctx, func(txCtx context.Context) error {
		// Acquire the domain row before the shared authorization fence, matching
		// permission writer lock ordering. Authorization is repeated for replays.
		before, err := r.repository.Lock(txCtx, caller.OwnerID, request.ApplicationID)
		if err != nil {
			return err
		}
		_, allowed, err := r.authorizer.AuthorizeSystemPermissionAuthoring(txCtx, caller.PermissionFloor, &caller.Actor, string(constants.PermCustomizeApplication))
		if err != nil {
			return err
		}
		if !allowed {
			return navigationFailure(common.FailureForbidden)
		}
		replay, found, err := r.repository.Replay(txCtx, caller.OwnerID, caller.Actor.ID, request)
		if err != nil {
			return err
		}
		if found {
			result = replay
			return nil
		}
		if err := r.validateResourceTargets(txCtx, before, request.Items); err != nil {
			return err
		}
		result, err = r.repository.Store(txCtx, caller.OwnerID, caller.Actor.ID, request, before)
		return err
	})
	return result, err
}
func (r *ApplicationNavigationRuntime) Resolve(ctx context.Context, caller NavigationCaller, request semantic.ResolveRequest) ([]semantic.Document, error) {
	if err := semantic.ValidateResolveRequest(ctx, request); err != nil {
		return nil, err
	}
	if !validNavigationCaller(caller) {
		return nil, navigationFailure(common.FailureUnauthenticated)
	}
	result := make([]semantic.Document, 0, len(request.ApplicationIDs))
	err := r.transactions.WithTransaction(ctx, func(txCtx context.Context) error {
		authority, err := r.permissions.EffectivePermissionsSelectedInTransaction(txCtx, &caller.Actor, "", effective.Selection{Sections: []effective.Section{effective.SectionApplicationPermissions, effective.SectionNavigationPermissions}}, caller.PermissionFloor)
		if err != nil {
			return err
		}
		if authority == nil {
			return navigationFailure(common.FailureInternal)
		}
		apps := map[string]bool{}
		for _, p := range authority.ApplicationPermissions {
			apps[strings.ToLower(p.ApplicationID)] = p.CanView
		}
		tabs := map[string]bool{}
		for _, p := range authority.NavigationPermissions {
			tabs[strings.ToLower(p.NavigationKey)] = p.CanView
		}
		ids := make([]string, 0, len(request.ApplicationIDs))
		for _, id := range request.ApplicationIDs {
			if apps[strings.ToLower(id)] {
				ids = append(ids, id)
			}
		}
		documents, err := r.repository.ReadMany(txCtx, caller.OwnerID, ids)
		if err != nil {
			return err
		}
		for _, document := range documents {
			visible := make([]semantic.Item, 0, len(document.Items))
			for _, item := range document.Items {
				if navigationItemVisible(item, tabs) {
					visible = append(visible, item)
				}
			}
			document.Items = visible
			result = append(result, document)
		}
		return nil
	})
	return result, err
}

// Collection targets use their modeled resource identity; registered pages use
// their stable page identity. Record and external links do not create tab grants;
// destination access remains enforced by the owning record/page service.
func navigationItemVisible(item semantic.Item, tabs map[string]bool) bool {
	if resource := item.Target.Resource; resource != nil && resource.ResourceID == "" {
		return tabs[strings.ToLower(resource.ResourceType)]
	}
	if page := item.Target.Page; page != nil {
		return tabs[strings.ToLower(page.ID)]
	}
	return true
}

// Validate new references after replay lookup. Existing targets remain editable
// even when their resource has since been removed; saving labels or order must
// not destroy a full document's otherwise hidden or temporarily unavailable items.
func (r *ApplicationNavigationRuntime) validateResourceTargets(ctx context.Context, before semantic.Document, items []semantic.Item) error {
	existing := map[semantic.ResourceTarget]bool{}
	for _, item := range before.Items {
		if target := item.Target.Resource; target != nil {
			existing[*target] = true
		}
	}
	checked := map[string]bool{}
	for _, item := range items {
		target := item.Target.Resource
		if target == nil || existing[*target] || checked[target.ResourceType] {
			continue
		}
		if r.schemas == nil {
			return navigationFailure(common.FailureUnavailable)
		}
		schema, err := r.schemas.GetSchemaByAPIName(ctx, target.ResourceType)
		if err != nil {
			return err
		}
		if schema == nil {
			return navigationFailure(common.FailureInvalidRequest)
		}
		checked[target.ResourceType] = true
	}
	return nil
}
