package permissions

import (
	"context"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

// ContractPolicy projects the permission engine's current model carrier into
// the neutral policy contract.
type ContractPolicy struct {
	permissions *PermissionService
}

func NewContractPolicy(permissions *PermissionService) *ContractPolicy {
	return &ContractPolicy{permissions: permissions}
}

func (p *ContractPolicy) Access(ctx context.Context, user *models.UserSession, objectName string, req contract.AccessRequest) (*contract.EffectiveObjectAccess, error) {
	if !req.IncludeFields && !req.IncludeRecordTypes {
		return &contract.EffectiveObjectAccess{Object: contract.ObjectAccess{
			Create:     p.permissions.CheckObjectPermissionWithUser(ctx, objectName, constants.PermCreate, user),
			Read:       p.permissions.CheckObjectPermissionWithUser(ctx, objectName, constants.PermRead, user),
			Update:     p.permissions.CheckObjectPermissionWithUser(ctx, objectName, constants.PermEdit, user),
			Delete:     p.permissions.CheckObjectPermissionWithUser(ctx, objectName, constants.PermDelete, user),
			Deactivate: p.permissions.CheckObjectDeactivationPermissionWithUser(ctx, objectName, user),
		}}, nil
	}

	access, err := p.permissions.EffectiveAccess(ctx, user, objectName)
	if err != nil {
		return nil, err
	}
	out := contractEffectiveObjectAccessFromModel(access)
	if out == nil {
		return nil, nil
	}
	if !req.IncludeFields {
		out.Fields = nil
	}
	if !req.IncludeRecordTypes {
		out.RecordTypes = nil
		out.DefaultRecordTypeID = ""
		out.HasConcreteRecordTypes = false
		out.AvailableConcreteCount = 0
	}
	return out, nil
}

func (p *ContractPolicy) HasCapability(ctx context.Context, user *models.UserSession, capability contract.Capability) bool {
	if p == nil || p.permissions == nil {
		return false
	}
	return p.permissions.HasCapability(ctx, user, constants.Permission(capability))
}

func contractEffectiveObjectAccessFromModel(access *models.EffectiveObjectAccess) *contract.EffectiveObjectAccess {
	if access == nil {
		return nil
	}
	return &contract.EffectiveObjectAccess{
		Object: contract.ObjectAccess{
			Create:     access.Object.Create,
			Read:       access.Object.Read,
			Update:     access.Object.Update,
			Delete:     access.Object.Delete,
			Deactivate: access.Object.Deactivate,
		},
		Fields:                 contractFieldAccessMapFromModel(access.Fields),
		RecordTypes:            contractRecordTypeAvailabilityFromModel(access.RecordTypes),
		DefaultRecordTypeID:    access.DefaultRecordTypeID,
		HasConcreteRecordTypes: access.HasConcreteRecordTypes,
		AvailableConcreteCount: access.AvailableConcreteCount,
	}
}

func contractFieldAccessMapFromModel(fields map[string]models.FieldAccess) map[string]contract.FieldAccess {
	if fields == nil {
		return nil
	}
	out := make(map[string]contract.FieldAccess, len(fields))
	for name, access := range fields {
		out[name] = contract.FieldAccess{
			Createable: access.Createable,
			Updateable: access.Updateable,
			Readable:   access.Readable,
		}
	}
	return out
}

func contractRecordTypeAvailabilityFromModel(recordTypes []models.RecordTypeAvailability) []contract.RecordTypeAvailability {
	if recordTypes == nil {
		return nil
	}
	out := make([]contract.RecordTypeAvailability, 0, len(recordTypes))
	for _, rt := range recordTypes {
		out = append(out, contract.RecordTypeAvailability{
			ID:        rt.ID,
			APIName:   rt.DeveloperName,
			Name:      rt.Name,
			Available: rt.Available,
			Master:    rt.Master,
			Default:   rt.Default,
		})
	}
	return out
}
