package permissions

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/fieldpermissions"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionauthoringv1"
)

// PermissionAuthoringSnapshotStore is the one persistence projection used to
// assemble the complete five-family stored authoring image. Every method runs
// inside the same caller-owned current-read transaction after the canonical
// PermissionSet root has been locked.
type PermissionAuthoringSnapshotStore interface {
	ports.PermissionAuthoringTargetStore
	ports.PermissionAuthoringGrantReader
	ListSystemPermissions(context.Context) ([]models.SystemPermission, error)
	GetPermissionSetGrants(context.Context, string) (map[string]bool, error)
	ListRecordTypeCatalog(context.Context) ([]ports.RecordTypeCatalogEntry, error)
	GetPermissionSetRecordTypeGrants(context.Context, string) ([]models.PermissionSetRecordTypeVisibility, error)
	ListCustomPermissions(context.Context) ([]models.SystemCustomPermission, error)
	GetPermissionSetCustomPermGrants(context.Context, string) ([]models.PermissionSetCustomPermission, error)
}

// PermissionAuthoringSnapshotService owns one coherent authoring read. It is
// deliberately separate from UI composition: object/field shape remains on
// the request-bound Catalog capability, while this service owns stored grant
// state and the compare token for one canonical PermissionSet.
type PermissionAuthoringSnapshotService struct {
	transactions PermissionAuthoringTransactions
	store        PermissionAuthoringSnapshotStore
	policy       PermissionAuthoringPolicy
	receipts     PermissionAuthoringReceiptSource
}

func NewPermissionAuthoringSnapshotService(
	transactions PermissionAuthoringTransactions,
	store PermissionAuthoringSnapshotStore,
	policy PermissionAuthoringPolicy,
	receipts PermissionAuthoringReceiptSource,
) (*PermissionAuthoringSnapshotService, error) {
	if transactions == nil || store == nil || policy == nil || receipts == nil {
		return nil, errors.New("permission authoring snapshot dependencies are incomplete")
	}
	return &PermissionAuthoringSnapshotService{
		transactions: transactions,
		store:        store,
		policy:       policy,
		receipts:     receipts,
	}, nil
}

func (s *PermissionAuthoringSnapshotService) Read(
	ctx context.Context,
	invocation PermissionAuthoringInvocation,
	request permissionauthoringv1.SnapshotRequest,
) (permissionauthoringv1.SnapshotResponse, error) {
	if err := permissionauthoringv1.ValidateSnapshotRequest(ctx, request); err != nil {
		return permissionauthoringv1.SnapshotResponse{}, err
	}
	if err := validatePermissionAuthoringInvocation(invocation, true); err != nil {
		return permissionauthoringv1.SnapshotResponse{}, err
	}

	var response permissionauthoringv1.SnapshotResponse
	err := s.transactions.WithCurrentReadPermissionAuthoringTransaction(ctx, func(txCtx context.Context) error {
		receipts, err := s.receipts.PermissionAuthoringReceipts(txCtx)
		if err != nil {
			return err
		}
		if request.MinCatalogRevision != nil && receipts.CatalogRevision < *request.MinCatalogRevision {
			return &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureStaleState}
		}

		permissionSetID, found, err := s.store.ResolvePermissionAuthoringTarget(
			txCtx,
			string(request.Target.Kind),
			request.Target.ID,
		)
		if err != nil {
			return err
		}
		if !found {
			_, authorized, authorizeErr := s.policy.AuthorizePermissionSetAuthoring(
				txCtx,
				invocation.AdmittedPermissionStateFloor,
				invocation.Actor,
			)
			if authorizeErr != nil {
				return authorizeErr
			}
			if !authorized {
				return &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureForbidden}
			}
			return &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureMissing}
		}

		aggregate, found, err := s.store.LockPermissionSetAuthoring(txCtx, permissionSetID)
		if err != nil {
			return err
		}
		if !found {
			return &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureMissing}
		}
		if aggregate.CanonicalPermissionSetID != permissionSetID || !permissionAuthoringTargetMatchesRole(request.Target.Kind, aggregate.Role) {
			return errors.New("permission authoring snapshot aggregate role disagrees with selected target")
		}
		revision := aggregate.Revision
		authorization, authorized, err := s.policy.AuthorizePermissionSetAuthoring(
			txCtx,
			invocation.AdmittedPermissionStateFloor,
			invocation.Actor,
		)
		if err != nil {
			return err
		}
		if !authorized {
			return &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureForbidden}
		}
		if authorization == nil || authorization.FormulaActor == nil {
			return errors.New("permission authoring snapshot policy returned incomplete current authority")
		}
		txCtx = WithEffectivePermissions(
			txCtx,
			authorization.FormulaActor.ID,
			authorization.FormulaActor.ProfileID,
			authorization.SystemPermissions,
			authorization.CustomPermissions,
		)
		if code, checkErr := s.policy.CheckPermissionSetAuthorable(txCtx, permissionSetID); checkErr != nil {
			return checkErr
		} else if code == permissionauthoringv1.RejectionMissing {
			return &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureMissing}
		} else if code != permissionauthoringv1.RejectionNone {
			return &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureForbidden}
		}

		catalog, grants, err := readPermissionAuthoringStoredImage(txCtx, s.store, permissionSetID)
		if err != nil {
			return err
		}
		token, err := permissionauthoringv1.NewAuthoringToken(permissionSetID, revision)
		if err != nil {
			return err
		}
		response = permissionauthoringv1.NormalizeSnapshotResponse(permissionauthoringv1.SnapshotResponse{
			CanonicalPermissionSetID: permissionSetID,
			AuthoringToken:           token,
			Catalog:                  catalog,
			Grants:                   grants,
			Receipts:                 receipts,
		})
		return permissionauthoringv1.ValidateSnapshotResponse(request, response)
	})
	if err != nil {
		return permissionauthoringv1.SnapshotResponse{}, err
	}
	return response, nil
}

func readPermissionAuthoringStoredImage(
	ctx context.Context,
	store PermissionAuthoringSnapshotStore,
	permissionSetID string,
) (permissionauthoringv1.SnapshotCatalog, permissionauthoringv1.StoredGrants, error) {
	systemCatalog, err := store.ListSystemPermissions(ctx)
	if err != nil {
		return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{}, err
	}
	systemGrants, err := store.GetPermissionSetGrants(ctx, permissionSetID)
	if err != nil {
		return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{}, err
	}
	objectRows, err := store.ListPermissionAuthoringObjectGrants(ctx, permissionSetID)
	if err != nil {
		return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{}, err
	}
	fieldRows, err := store.LockPermissionAuthoringFieldGrants(ctx, permissionSetID)
	if err != nil {
		return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{}, err
	}
	recordTypeCatalog, err := store.ListRecordTypeCatalog(ctx)
	if err != nil {
		return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{}, err
	}
	recordTypeRows, err := store.GetPermissionSetRecordTypeGrants(ctx, permissionSetID)
	if err != nil {
		return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{}, err
	}
	customCatalog, err := store.ListCustomPermissions(ctx)
	if err != nil {
		return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{}, err
	}
	customRows, err := store.GetPermissionSetCustomPermGrants(ctx, permissionSetID)
	if err != nil {
		return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{}, err
	}

	catalog := permissionauthoringv1.SnapshotCatalog{}
	grants := permissionauthoringv1.StoredGrants{}
	for _, permission := range systemCatalog {
		if !permission.IsActive {
			continue
		}
		description := ""
		if permission.Description != nil {
			description = *permission.Description
		}
		requires := ""
		if permission.RequiresPermission != nil {
			requires = *permission.RequiresPermission
		}
		catalog.System = append(catalog.System, permissionauthoringv1.SystemPermission{
			PermissionID:         permission.APIName,
			Label:                permission.Label,
			Category:             permission.Category,
			Description:          description,
			RequiresPermissionID: requires,
		})
		grants.System = append(grants.System, permissionauthoringv1.CapabilityGrant{
			CapabilityID: permission.APIName,
			Enabled:      systemGrants[permission.APIName],
		})
	}

	objects := make(map[string]permissionauthoringv1.ObjectGrant)
	for _, row := range objectRows {
		if row.Deleted {
			continue
		}
		key := strings.ToLower(strings.TrimSpace(row.ResourceType))
		grant := objects[key]
		if grant.ResourceType == "" {
			grant.ResourceType = strings.TrimSpace(row.ResourceType)
		}
		grant.Read = grant.Read || row.Read
		grant.Create = grant.Create || row.Create
		grant.Edit = grant.Edit || row.Edit
		grant.Delete = grant.Delete || row.Delete
		grant.ViewAll = grant.ViewAll || row.ViewAll
		grant.ModifyAll = grant.ModifyAll || row.ModifyAll
		objects[key] = grant
	}
	for _, grant := range objects {
		grants.Objects = append(grants.Objects, grant)
	}

	fields := make(map[string]permissionauthoringv1.FieldGrant)
	for _, row := range fieldRows {
		if row.Deleted {
			continue
		}
		resourceType := strings.TrimSpace(row.ResourceType)
		fieldName := fieldpermissions.FieldComponent(resourceType, row.FieldName)
		if resourceType == "" || fieldName == "" {
			return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{},
				fmt.Errorf("permission authoring snapshot found invalid field grant identity")
		}
		key := strings.ToLower(resourceType) + "\x00" + strings.ToLower(fieldName)
		grant := fields[key]
		if grant.ResourceType == "" {
			grant.ResourceType = resourceType
			grant.FieldName = fieldName
		}
		grant.Read = grant.Read || row.Read
		grant.Edit = grant.Edit || row.Edit
		fields[key] = grant
	}
	for _, grant := range fields {
		grants.Fields = append(grants.Fields, grant)
	}

	activeRecordTypes := make(map[string]struct{}, len(recordTypeCatalog))
	for _, recordType := range recordTypeCatalog {
		if !recordType.IsActive {
			continue
		}
		if _, duplicate := activeRecordTypes[recordType.RecordTypeID]; duplicate {
			return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{},
				fmt.Errorf("permission authoring snapshot found duplicate active record type identity %q", recordType.RecordTypeID)
		}
		activeRecordTypes[recordType.RecordTypeID] = struct{}{}
		catalog.RecordTypes = append(catalog.RecordTypes, permissionauthoringv1.RecordType{
			RecordTypeID: recordType.RecordTypeID,
			ResourceType: recordType.ObjectAPIName,
			StableName:   recordType.Name,
			Label:        recordType.Label,
			Active:       true,
		})
	}
	recordTypeGrants := make(map[string]struct{}, len(recordTypeRows))
	for _, row := range recordTypeRows {
		if _, active := activeRecordTypes[row.RecordTypeID]; !active {
			continue
		}
		if _, duplicate := recordTypeGrants[row.RecordTypeID]; duplicate {
			return permissionauthoringv1.SnapshotCatalog{}, permissionauthoringv1.StoredGrants{},
				fmt.Errorf("permission authoring snapshot found duplicate record type grant identity %q", row.RecordTypeID)
		}
		recordTypeGrants[row.RecordTypeID] = struct{}{}
		grants.RecordTypes = append(grants.RecordTypes, permissionauthoringv1.RecordTypeGrant{
			RecordTypeID: row.RecordTypeID,
			Visible:      row.IsVisible,
		})
	}

	customGrants := make(map[string]bool, len(customRows))
	for _, row := range customRows {
		customGrants[row.CustomPermissionID] = true
	}
	for _, permission := range customCatalog {
		if !permission.IsActive || permission.IsDeleted {
			continue
		}
		catalog.Custom = append(catalog.Custom, permissionauthoringv1.CustomPermission{
			PermissionID: permission.ID,
			StableName:   permission.APIName,
			Label:        permission.Label,
		})
		grants.Custom = append(grants.Custom, permissionauthoringv1.CapabilityGrant{
			CapabilityID: permission.ID,
			Enabled:      customGrants[permission.ID],
		})
	}
	return catalog, grants, nil
}

// Status reads only the principal-scoped durable journal. It deliberately
// never re-resolves Target.Profile or reauthorizes against the selected
// PermissionSet, so later Profile rebinding or target deletion cannot redirect
// a historical command.
func (s *PermissionAuthoringService) Status(
	ctx context.Context,
	invocation PermissionAuthoringInvocation,
	request permissionauthoringv1.StatusRequest,
) (permissionauthoringv1.StatusResponse, error) {
	if err := permissionauthoringv1.ValidateStatusRequest(ctx, request); err != nil {
		return permissionauthoringv1.StatusResponse{}, err
	}
	if err := validatePermissionAuthoringInvocation(invocation, false); err != nil {
		return permissionauthoringv1.StatusResponse{}, err
	}
	row, err := s.commands.LoadActive(
		ctx,
		permissionAuthoringScopeDigest(invocation.AuthorityScope, invocation.Actor.ID),
		request.CommandID,
	)
	if errors.Is(err, ports.ErrPermissionAuthoringCommandMissing) {
		return permissionauthoringv1.StatusResponse{}, &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureMissing}
	}
	if err != nil {
		return permissionauthoringv1.StatusResponse{}, err
	}
	if row.RequestDigest != request.CommandDigest {
		return permissionauthoringv1.StatusResponse{}, invalidPermissionAuthoringIdentity()
	}
	response := permissionauthoringv1.StatusResponse{
		CommandID:     request.CommandID,
		CommandDigest: request.CommandDigest,
		State:         permissionauthoringv1.CommandAccepted,
	}
	if row.State == ports.PermissionAuthoringCommandCompleted {
		var terminal permissionauthoringv1.Response
		if err := json.Unmarshal(row.ResultPayload, &terminal); err != nil {
			return permissionauthoringv1.StatusResponse{}, fmt.Errorf("decode permission authoring status terminal: %w", err)
		}
		response.State = permissionauthoringv1.CommandCompleted
		response.Result = &terminal
	} else if row.State != ports.PermissionAuthoringCommandAccepted {
		return permissionauthoringv1.StatusResponse{}, fmt.Errorf("permission authoring journal has unsupported state %q", row.State)
	}
	if err := permissionauthoringv1.ValidateStatusResponse(request, response); err != nil {
		return permissionauthoringv1.StatusResponse{}, fmt.Errorf("validate permission authoring status: %w", err)
	}
	return response, nil
}

func validatePermissionAuthoringInvocation(invocation PermissionAuthoringInvocation, requireFloor bool) error {
	invocation.AuthorityScope = strings.TrimSpace(invocation.AuthorityScope)
	if invocation.AuthorityScope == "" || invocation.Actor == nil || strings.TrimSpace(invocation.Actor.ID) == "" ||
		(requireFloor && invocation.AdmittedPermissionStateFloor.Validate() != nil) {
		return &permissionauthoringv1.Failure{Category: permissionauthoringv1.FailureUnauthenticated}
	}
	return nil
}
