package permissions

import (
	"context"
	"fmt"
	"strings"

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

type accessPolicyEvalState struct {
	objectOps map[string]bool
	exprOps   map[string]bool
}

func newAccessPolicyEvalState() *accessPolicyEvalState {
	return &accessPolicyEvalState{
		objectOps: make(map[string]bool),
		exprOps:   make(map[string]bool),
	}
}

func normalizePolicyOperation(operation string) string {
	switch strings.ToLower(strings.TrimSpace(operation)) {
	case "objectread":
		return "objectRead"
	case "objectcreate":
		return "objectCreate"
	case "objectedit":
		return "objectEdit"
	case "objectdelete":
		return "objectDelete"
	case strings.ToLower(constants.PermRead):
		return "objectRead"
	case strings.ToLower(constants.PermCreate):
		return "objectCreate"
	case strings.ToLower(constants.PermEdit):
		return "objectEdit"
	case strings.ToLower(constants.PermDelete):
		return "objectDelete"
	case "fieldread":
		return "fieldRead"
	case "fieldedit":
		return "fieldEdit"
	default:
		return ""
	}
}

func (ps *PermissionService) resolvePolicyExpr(policy *models.ObjectAccessPolicy, operation string) *models.AccessPolicyExpr {
	if policy == nil {
		return nil
	}
	switch normalizePolicyOperation(operation) {
	case "objectRead":
		return policy.ObjectRead
	case "objectCreate":
		return policy.ObjectCreate
	case "objectEdit":
		return policy.ObjectEdit
	case "objectDelete":
		return policy.ObjectDelete
	case "fieldRead":
		if policy.FieldRead != nil {
			return policy.FieldRead
		}
		if policy.IsDerived() {
			return &models.AccessPolicyExpr{Inherit: "objectRead"}
		}
	case "fieldEdit":
		if policy.FieldEdit != nil {
			return policy.FieldEdit
		}
		if policy.IsDerived() {
			return &models.AccessPolicyExpr{Inherit: "objectEdit"}
		}
	}
	return nil
}

func (ps *PermissionService) EvaluateObjectAccessPolicy(
	ctx context.Context,
	schema *models.ObjectMetadata,
	operation string,
	user *models.UserSession,
) (bool, error) {
	return ps.evaluateObjectAccessPolicy(ctx, schema, nil, operation, user, newAccessPolicyEvalState())
}

func (ps *PermissionService) evaluateObjectAccessPolicy(
	ctx context.Context,
	schema *models.ObjectMetadata,
	record models.SObject,
	operation string,
	user *models.UserSession,
	state *accessPolicyEvalState,
) (bool, error) {
	if schema == nil || schema.AccessPolicy == nil {
		return false, nil
	}
	expr := ps.resolvePolicyExpr(schema.AccessPolicy, operation)
	if expr == nil {
		return false, nil
	}
	return ps.evaluateAccessPolicyExpr(ctx, schema, schema.AccessPolicy, expr, record, operation, user, state)
}

func accessPolicyExprNeedsRecord(expr *models.AccessPolicyExpr) bool {
	if expr == nil {
		return false
	}
	if strings.EqualFold(strings.TrimSpace(expr.Kind), "recordFieldEqualsCurrentUser") {
		return true
	}
	for i := range expr.AllOf {
		if accessPolicyExprNeedsRecord(&expr.AllOf[i]) {
			return true
		}
	}
	for i := range expr.AnyOf {
		if accessPolicyExprNeedsRecord(&expr.AnyOf[i]) {
			return true
		}
	}
	if accessPolicyExprNeedsRecord(expr.Not) {
		return true
	}
	return false
}

func (ps *PermissionService) accessPolicyRequiresRecordContext(schema *models.ObjectMetadata, operation string) bool {
	if schema == nil || schema.AccessPolicy == nil {
		return false
	}
	return accessPolicyExprNeedsRecord(ps.resolvePolicyExpr(schema.AccessPolicy, operation))
}

func (ps *PermissionService) AccessPolicyRequiresRecordContext(schema *models.ObjectMetadata, operation string) bool {
	return ps.accessPolicyRequiresRecordContext(schema, operation)
}

func currentUserMatchesRecordField(record models.SObject, fieldName string, user *models.UserSession) bool {
	if record == nil || user == nil || strings.TrimSpace(fieldName) == "" {
		return false
	}
	raw, ok := record[strings.TrimSpace(fieldName)]
	if !ok || raw == nil {
		return false
	}
	switch v := raw.(type) {
	case string:
		return v == user.ID
	case *string:
		return v != nil && *v == user.ID
	default:
		return fmt.Sprintf("%v", v) == user.ID
	}
}

func (ps *PermissionService) evaluateAccessPolicyExpr(
	ctx context.Context,
	schema *models.ObjectMetadata,
	policy *models.ObjectAccessPolicy,
	expr *models.AccessPolicyExpr,
	record models.SObject,
	operation string,
	user *models.UserSession,
	state *accessPolicyEvalState,
) (bool, error) {
	if expr == nil {
		return false, nil
	}

	if len(expr.AllOf) > 0 {
		for i := range expr.AllOf {
			ok, err := ps.evaluateAccessPolicyExpr(ctx, schema, policy, &expr.AllOf[i], record, operation, user, state)
			if err != nil || !ok {
				return ok, err
			}
		}
		return true, nil
	}
	if len(expr.AnyOf) > 0 {
		for i := range expr.AnyOf {
			ok, err := ps.evaluateAccessPolicyExpr(ctx, schema, policy, &expr.AnyOf[i], record, operation, user, state)
			if err == nil && ok {
				return true, nil
			}
			if err != nil {
				return false, err
			}
		}
		return false, nil
	}
	if expr.Not != nil {
		ok, err := ps.evaluateAccessPolicyExpr(ctx, schema, policy, expr.Not, record, operation, user, state)
		if err != nil {
			return false, err
		}
		return !ok, nil
	}
	if expr.Inherit != "" {
		targetOp := normalizePolicyOperation(expr.Inherit)
		if targetOp == "" {
			return false, fmt.Errorf("unsupported access policy inherit target %q", expr.Inherit)
		}
		key := schema.APIName + ":" + targetOp
		if state.exprOps[key] {
			return false, fmt.Errorf("access policy inherit cycle on %s %s", schema.APIName, targetOp)
		}
		state.exprOps[key] = true
		defer delete(state.exprOps, key)
		target := ps.resolvePolicyExpr(policy, targetOp)
		if target == nil {
			return false, nil
		}
		return ps.evaluateAccessPolicyExpr(ctx, schema, policy, target, record, targetOp, user, state)
	}

	switch strings.ToLower(strings.TrimSpace(expr.Kind)) {
	case "objectpermission":
		refObject := strings.TrimSpace(expr.Object)
		refOp := strings.TrimSpace(expr.Operation)
		if refObject == "" || refOp == "" {
			return false, fmt.Errorf("objectPermission term requires object and operation")
		}
		return ps.checkObjectPermissionWithState(ctx, refObject, refOp, user, state), nil
	case "systempermission":
		perm := strings.TrimSpace(expr.APIName)
		if perm == "" {
			return false, fmt.Errorf("systemPermission term requires apiName")
		}
		return ps.CheckSystemPermission(ctx, user, perm), nil
	case "recordfieldequalscurrentuser":
		fieldName := strings.TrimSpace(expr.Field)
		if fieldName == "" {
			return false, fmt.Errorf("recordFieldEqualsCurrentUser term requires field")
		}
		return currentUserMatchesRecordField(record, fieldName, user), nil
	default:
		return false, fmt.Errorf("unsupported access policy expression")
	}
}
