package analytics

import (
	"context"
	"fmt"
	"strings"

	pkgErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

func splitCorrelatedMemberships(predicate queryspec.Predicate) (queryspec.Predicate, []queryspec.CorrelatedMembership, error) {
	switch predicate.Kind {
	case queryspec.PredicateNone:
		return queryspec.Predicate{}, nil, nil
	case queryspec.PredicateCorrelatedMembership:
		if predicate.CorrelatedMembership == nil {
			return queryspec.Predicate{}, nil, pkgErrors.NewValidationError("Filter", "correlated membership predicate is incomplete")
		}
		return queryspec.Predicate{}, []queryspec.CorrelatedMembership{*predicate.CorrelatedMembership}, nil
	case queryspec.PredicateAnd:
		remaining := make([]queryspec.Predicate, 0, len(predicate.Children))
		memberships := make([]queryspec.CorrelatedMembership, 0)
		for _, child := range predicate.Children {
			childRemaining, childMemberships, err := splitCorrelatedMemberships(child)
			if err != nil {
				return queryspec.Predicate{}, nil, err
			}
			if childRemaining.Kind != queryspec.PredicateNone {
				remaining = append(remaining, childRemaining)
			}
			memberships = append(memberships, childMemberships...)
		}
		return queryspec.And(remaining...), memberships, nil
	default:
		if predicateContainsCorrelatedMembership(predicate) {
			return queryspec.Predicate{}, nil, pkgErrors.NewValidationError("Filter", "correlated membership predicates are only supported as top-level AND terms in row queries")
		}
		return predicate, nil, nil
	}
}

func predicateContainsCorrelatedMembership(predicate queryspec.Predicate) bool {
	if predicate.Kind == queryspec.PredicateCorrelatedMembership {
		return true
	}
	for _, child := range predicate.Children {
		if predicateContainsCorrelatedMembership(child) {
			return true
		}
	}
	return false
}

func (qs *QueryService) buildCorrelatedMembershipPlans(
	ctx context.Context,
	outerObject string,
	outerSchema *models.ObjectMetadata,
	memberships []queryspec.CorrelatedMembership,
	currentUser *models.UserSession,
) ([]ports.QueryCorrelatedMembershipPlan, error) {
	if len(memberships) == 0 {
		return nil, nil
	}
	plans := make([]ports.QueryCorrelatedMembershipPlan, 0, len(memberships))
	for i, membership := range memberships {
		plan, err := qs.buildCorrelatedMembershipPlan(ctx, outerObject, outerSchema, membership, i, currentUser)
		if err != nil {
			return nil, err
		}
		plans = append(plans, plan)
	}
	return plans, nil
}

func (qs *QueryService) buildCorrelatedMembershipPlan(
	ctx context.Context,
	outerObject string,
	outerSchema *models.ObjectMetadata,
	membership queryspec.CorrelatedMembership,
	index int,
	currentUser *models.UserSession,
) (ports.QueryCorrelatedMembershipPlan, error) {
	outerField, err := qs.validateCorrelatedMembershipField(ctx, outerObject, outerSchema, membership.OuterField, "Filter", currentUser)
	if err != nil {
		return ports.QueryCorrelatedMembershipPlan{}, err
	}
	innerObject := strings.TrimSpace(membership.InnerObjectAPIName)
	if innerObject == "" {
		return ports.QueryCorrelatedMembershipPlan{}, pkgErrors.NewValidationError("Filter", "correlated membership requires an inner object")
	}
	if err := qs.checkObjectReadAccess(ctx, innerObject, currentUser); err != nil {
		return ports.QueryCorrelatedMembershipPlan{}, err
	}
	innerSchema := qs.metadata.GetSchema(ctx, innerObject)
	if innerSchema == nil {
		return ports.QueryCorrelatedMembershipPlan{}, pkgErrors.NewNotFoundError("Object", innerObject)
	}
	innerSelectField, err := qs.validateCorrelatedMembershipField(ctx, innerObject, innerSchema, membership.InnerSelectField, "Filter", currentUser)
	if err != nil {
		return ports.QueryCorrelatedMembershipPlan{}, err
	}
	if membership.InnerLimit < 0 {
		return ports.QueryCorrelatedMembershipPlan{}, pkgErrors.NewValidationError("Filter", "correlated membership inner limit must be non-negative")
	}

	innerFilter, nestedMemberships, err := splitCorrelatedMemberships(membership.InnerFilter)
	if err != nil {
		return ports.QueryCorrelatedMembershipPlan{}, err
	}
	if len(nestedMemberships) > 0 {
		return ports.QueryCorrelatedMembershipPlan{}, pkgErrors.NewValidationError("Filter", "nested correlated membership predicates are not supported in row queries")
	}
	loweredInnerFilter, err := lowerQueryFilterPredicate(innerFilter)
	if err != nil {
		return ports.QueryCorrelatedMembershipPlan{}, err
	}

	innerReq := models.QueryRequest{
		ObjectAPIName: innerSchema.APIName,
		FilterExpr:    loweredInnerFilter.expression,
		Criteria:      modelQueryCriteria(loweredInnerFilter.criteria),
		Limit:         membership.InnerLimit,
		ScanAll:       membership.InnerScanAll,
		SelectFields:  []string{innerSelectField},
	}
	parentFilterPaths, err := qs.validateQueryReadClauses(ctx, innerSchema.APIName, innerSchema, innerReq, currentUser)
	if err != nil {
		return ports.QueryCorrelatedMembershipPlan{}, err
	}
	innerReq.ParentFilterPaths = parentFilterPaths

	id := strings.TrimSpace(membership.ID)
	if id == "" {
		id = fmt.Sprintf("correlated_%d", index)
	}
	return ports.QueryCorrelatedMembershipPlan{
		ID:               id,
		OuterField:       outerField,
		Negate:           membership.Negate,
		InnerSchema:      innerSchema,
		InnerSelectField: innerSelectField,
		InnerRequest:     innerReq,
		InnerRLSResult:   qs.readRLSResult(ctx, innerSchema, innerSchema.APIName, currentUser),
	}, nil
}

func (qs *QueryService) validateCorrelatedMembershipField(ctx context.Context, objectName string, schema *models.ObjectMetadata, rawField string, clause string, currentUser *models.UserSession) (string, error) {
	fieldName := strings.Trim(strings.TrimSpace(rawField), "`")
	if fieldName == "" {
		return "", pkgErrors.NewValidationError(clause, "correlated membership requires a field")
	}
	if strings.Contains(fieldName, ".") {
		return "", pkgErrors.NewValidationError(clause, fmt.Sprintf("correlated membership requires a direct field: %s", fieldName))
	}
	field := models.FindField(schema, fieldName)
	if field == nil {
		return "", pkgErrors.NewValidationError(clause, fmt.Sprintf("correlated membership field is not declared: %s.%s", objectName, fieldName))
	}
	if err := qs.validateClauseFieldReadable(ctx, objectName, schema, field.APIName, clause, currentUser); err != nil {
		return "", err
	}
	return field.APIName, nil
}
