package rls

import (
	"fmt"
	"strings"

	"github.com/nexuscrm/backend/pkg/utils"
	"github.com/nexuscrm/shared/pkg/constants"
)

// Result holds pre-computed record-level security data for a (user, object) pair.
// It is the domain contract between permission resolution and query adapters.
type Result struct {
	Skip                     bool
	DenyAll                  bool
	ObjectAPIName            string
	HasOwnerField            bool
	UserID                   string
	OwnerUserIDs             []string
	SharingRuleOwnerIDs      []string
	SharingRuleFromGroupIDs  []string
	IncludeGroupOwnership    bool
	IncludeManualShares      bool
	IncludeTeamMembers       bool
	ParentFilter             *ParentFilter
	// CriteriaSQLFragments holds pre-compiled SQL WHERE fragments from
	// criteria-based sharing rules. Each fragment is a self-contained
	// boolean expression (e.g., "`Industry` = 'Technology'") produced
	// by the expression engine's ToSQL compiler at BuildRLSResult time.
	CriteriaSQLFragments []string
	AllowedIDs           []string // Pre-computed list of allowed IDs (used by FolderSharing/FolderContentSharing)
	AllowedIDsColumn     string   // Column to filter on (default "Id"; "FolderId" for folder-content objects)
}

// ParentFilter enables ControlledByParent sharing (e.g., Contact inherits Account access).
type ParentFilter struct {
	ParentObjectAPIName string
	ForeignKeyField     string
	ParentRLSResult     *Result
}

// ToSQLFilter converts an RLS result into a SQL WHERE fragment.
// tableQualifier is the physical FROM target (view name, alias, or table name)
// used to qualify Id references in manual-share / team-member subqueries.
// Pass "" to default to the logical ObjectAPIName.
// Returns "" when the object is unrestricted (Skip=true).
func ToSQLFilter(result *Result, tableQualifier string) string {
	if result == nil || result.Skip {
		return ""
	}
	if result.DenyAll {
		return "1 = 0"
	}

	// Resolve table qualifier: callers provide the physical FROM target;
	// fall back to logical object name (correct for standalone subqueries
	// like ParentFilter where the subquery's own FROM uses the object name).
	tq := tableQualifier
	if tq == "" {
		tq = result.ObjectAPIName
	}

	// Handle pre-computed allowed IDs (e.g., FolderSharing / FolderContentSharing)
	if len(result.AllowedIDs) > 0 {
		col := "Id"
		if result.AllowedIDsColumn != "" {
			col = result.AllowedIDsColumn
		}
		placeholders := make([]string, len(result.AllowedIDs))
		for i, id := range result.AllowedIDs {
			placeholders[i] = fmt.Sprintf("'%s'", strings.ReplaceAll(id, "'", "''"))
		}
		return fmt.Sprintf("`%s`.`%s` IN (%s)", tq, col, strings.Join(placeholders, ","))
	}

	var parts []string

	if result.ParentFilter != nil {
		pf := result.ParentFilter
		// Parent subquery's FROM is always the parent object name, so pass "".
		parentExpr := ToSQLFilter(pf.ParentRLSResult, "")
		if parentExpr == "" {
			if pf.ParentRLSResult != nil && pf.ParentRLSResult.Skip {
				return ""
			}
		} else {
			parts = append(parts, fmt.Sprintf(
				"%s IN (SELECT Id FROM %s WHERE %s)",
				escapeIdentifier(pf.ForeignKeyField),
				escapeIdentifier(pf.ParentObjectAPIName),
				parentExpr,
			))
		}
	}

	if result.HasOwnerField {
		if len(result.OwnerUserIDs) > 0 {
			parts = append(parts, buildInClause(constants.FieldOwnerID, result.OwnerUserIDs))
		}
		if len(result.SharingRuleOwnerIDs) > 0 {
			parts = append(parts, buildInClause(constants.FieldOwnerID, result.SharingRuleOwnerIDs))
		}
		for _, groupID := range result.SharingRuleFromGroupIDs {
			parts = append(parts, fmt.Sprintf(
				"%s IN (SELECT gm.UserOrGroupId FROM GroupMember gm WHERE gm.GroupId = '%s')",
				escapeIdentifier(constants.FieldOwnerID),
				utils.EscapeSQLString(groupID),
			))
		}
		if result.IncludeGroupOwnership && result.UserID != "" {
			parts = append(parts, fmt.Sprintf(
				"%s IN (SELECT gm.GroupId FROM GroupMember gm WHERE gm.UserOrGroupId = '%s')",
				escapeIdentifier(constants.FieldOwnerID),
				utils.EscapeSQLString(result.UserID),
			))
		}
	}

	if result.IncludeManualShares && result.UserID != "" {
		parts = append(parts, fmt.Sprintf(
			"%s.Id IN (SELECT rs.RecordId FROM _System_RecordShare rs WHERE rs.ObjectApiName = '%s' AND (rs.ShareWithUserId = '%s' OR rs.ShareWithGroupId IN (SELECT gm2.GroupId FROM GroupMember gm2 WHERE gm2.UserOrGroupId = '%s')))",
			escapeIdentifier(tq),
			utils.EscapeSQLString(result.ObjectAPIName),
			utils.EscapeSQLString(result.UserID),
			utils.EscapeSQLString(result.UserID),
		))
	}

	if result.IncludeTeamMembers && result.UserID != "" {
		parts = append(parts, fmt.Sprintf(
			"%s.Id IN (SELECT tm.RecordId FROM _System_TeamMember tm WHERE tm.ObjectApiName = '%s' AND tm.UserId = '%s')",
			escapeIdentifier(tq),
			utils.EscapeSQLString(result.ObjectAPIName),
			utils.EscapeSQLString(result.UserID),
		))
	}

	// Criteria-based sharing rule fragments (pre-compiled by permission service)
	for _, frag := range result.CriteriaSQLFragments {
		parts = append(parts, frag)
	}

	if len(parts) == 0 {
		return "1 = 0"
	}
	if len(parts) == 1 {
		return parts[0]
	}
	return "(\n  " + strings.Join(parts, "\n  OR ") + "\n)"
}

func buildInClause(field string, ids []string) string {
	escaped := escapeIdentifier(field)
	if len(ids) == 1 {
		return fmt.Sprintf("%s = '%s'", escaped, utils.EscapeSQLString(ids[0]))
	}
	quoted := make([]string, len(ids))
	for i, id := range ids {
		quoted[i] = fmt.Sprintf("'%s'", utils.EscapeSQLString(id))
	}
	return fmt.Sprintf("%s IN (%s)", escaped, strings.Join(quoted, ", "))
}

func escapeIdentifier(s string) string {
	return "`" + strings.ReplaceAll(s, "`", "``") + "`"
}
