//go:build crm

package crm

import (
	"context"
	"fmt"
	"strconv"

	"github.com/nexuscrm/permissions"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

const assignmentPolicyEligibilityProjectionPageRows = 500

type assignmentPolicyEligibilityProjectionProvider struct{}

// newAssignmentPolicyEligibilityProjectionProvider returns the CRM product
// adapter that interprets assignment-policy selector keys as active Profile
// names and projects the complete active, non-deleted User population for
// those Profiles. The adapter is read-only and joins the caller's managed read
// store.
func newAssignmentPolicyEligibilityProjectionProvider() permissions.AssignmentPolicyEligibilityProjectionProvider {
	return assignmentPolicyEligibilityProjectionProvider{}
}

func (assignmentPolicyEligibilityProjectionProvider) AssignmentPolicyEligibilityReadSurfaces() []cp.ComponentReadSurface {
	return []cp.ComponentReadSurface{
		{Table: "Profile", Fact: "CRM assignment-policy Profile.Name selector resolution"},
		{Table: "User", Fact: "CRM assignment-policy active User eligibility population"},
	}
}

func (assignmentPolicyEligibilityProjectionProvider) ProjectAssignmentPolicyEligibility(
	ctx context.Context,
	store cp.ComponentManagedReadStore,
	selectorKeys []string,
) (permissions.AssignmentPolicyEligibilityProjection, error) {
	if len(selectorKeys) == 0 {
		return permissions.AssignmentPolicyEligibilityProjection{
			Resolution: permissions.AssignmentPolicyEligibilityInvalidSelector,
		}, nil
	}
	if store == nil {
		return permissions.AssignmentPolicyEligibilityProjection{}, fmt.Errorf(
			"CRM assignment-policy eligibility read store is required",
		)
	}
	paged, ok := store.(cp.ComponentManagedKeysetTransactionReadStore)
	if !ok {
		return permissions.AssignmentPolicyEligibilityProjection{}, fmt.Errorf(
			"CRM assignment-policy eligibility requires exact keyset reads",
		)
	}

	subjectIDs := make([]string, 0)
	for _, selector := range selectorKeys {
		// The managed SQL store evaluates this equality with the table's
		// utf8mb4_unicode_ci collation. MaximumRows=2 distinguishes one valid
		// selector from a missing or collation-ambiguous selector without
		// introducing a second collation implementation in the product adapter.
		profileRows, err := store.ReadRows(ctx, cp.ComponentManagedRowQuery{
			Table:       "Profile",
			Columns:     []string{"Id", "IsActive", "IsDeleted"},
			Equal:       cp.ComponentRow{"Name": selector},
			MaximumRows: 2,
		})
		if err != nil {
			return permissions.AssignmentPolicyEligibilityProjection{}, fmt.Errorf(
				"resolve CRM assignment-policy Profile selector %q: %w", selector, err,
			)
		}
		if len(profileRows) != 1 {
			return permissions.AssignmentPolicyEligibilityProjection{
				Resolution: permissions.AssignmentPolicyEligibilityInvalidSelector,
			}, nil
		}
		profileID, err := assignmentPolicyEligibilityRequiredString(profileRows[0], "Id")
		if err != nil {
			return permissions.AssignmentPolicyEligibilityProjection{}, err
		}
		active, err := assignmentPolicyEligibilityRequiredBool(profileRows[0], "IsActive")
		if err != nil {
			return permissions.AssignmentPolicyEligibilityProjection{}, err
		}
		deleted, err := assignmentPolicyEligibilityRequiredBool(profileRows[0], "IsDeleted")
		if err != nil {
			return permissions.AssignmentPolicyEligibilityProjection{}, err
		}
		if !active || deleted {
			return permissions.AssignmentPolicyEligibilityProjection{
				Resolution: permissions.AssignmentPolicyEligibilityInvalidSelector,
			}, nil
		}

		query := cp.ComponentManagedKeysetRowQuery{Query: cp.ComponentManagedRowQuery{
			Table:   "User",
			Columns: []string{"Id"},
			Equal: cp.ComponentRow{
				"ProfileId": profileID,
				"IsActive":  true,
				"IsDeleted": false,
			},
			OrderBy: []cp.ComponentManagedOrder{{
				Column: "Id", Direction: cp.ComponentManagedOrderAscending,
			}},
			LogicalKeyColumns: []string{"Id"},
			MaximumRows:       assignmentPolicyEligibilityProjectionPageRows,
		}}
		for {
			page, readErr := paged.ReadRowsKeysetPage(ctx, query)
			if readErr != nil {
				return permissions.AssignmentPolicyEligibilityProjection{}, fmt.Errorf(
					"project CRM assignment-policy Users for Profile %s: %w", profileID, readErr,
				)
			}
			for _, row := range page.Rows {
				subjectID, rowErr := assignmentPolicyEligibilityRequiredString(row, "Id")
				if rowErr != nil {
					return permissions.AssignmentPolicyEligibilityProjection{}, rowErr
				}
				subjectIDs = append(subjectIDs, subjectID)
			}
			if page.Exhausted {
				break
			}
			if len(page.Rows) == 0 {
				return permissions.AssignmentPolicyEligibilityProjection{}, fmt.Errorf(
					"project CRM assignment-policy Users for Profile %s: keyset read did not advance",
					profileID,
				)
			}
			lastID, rowErr := assignmentPolicyEligibilityRequiredString(page.Rows[len(page.Rows)-1], "Id")
			if rowErr != nil {
				return permissions.AssignmentPolicyEligibilityProjection{}, rowErr
			}
			query.ContinueAfter = cp.ComponentRow{"Id": lastID}
		}
	}

	return permissions.AssignmentPolicyEligibilityProjection{
		Resolution: permissions.AssignmentPolicyEligibilityResolved,
		SubjectIDs: subjectIDs,
	}, nil
}

func assignmentPolicyEligibilityRequiredString(row cp.ComponentRow, field string) (string, error) {
	value, ok := row[field]
	if !ok {
		return "", fmt.Errorf("CRM assignment-policy eligibility row is missing %s", field)
	}
	switch typed := value.(type) {
	case string:
		if typed != "" {
			return typed, nil
		}
	case []byte:
		if len(typed) != 0 {
			return string(typed), nil
		}
	}
	return "", fmt.Errorf(
		"CRM assignment-policy eligibility row field %s must be a non-empty string, got %T",
		field, value,
	)
}

func assignmentPolicyEligibilityRequiredBool(row cp.ComponentRow, field string) (bool, error) {
	value, ok := row[field]
	if !ok {
		return false, fmt.Errorf("CRM assignment-policy eligibility row is missing %s", field)
	}
	switch typed := value.(type) {
	case bool:
		return typed, nil
	case int64:
		if typed == 0 || typed == 1 {
			return typed == 1, nil
		}
	case uint64:
		if typed == 0 || typed == 1 {
			return typed == 1, nil
		}
	case string:
		if parsed, err := strconv.ParseBool(typed); err == nil {
			return parsed, nil
		}
	case []byte:
		if parsed, err := strconv.ParseBool(string(typed)); err == nil {
			return parsed, nil
		}
	}
	return false, fmt.Errorf(
		"CRM assignment-policy eligibility row field %s must be a boolean, got %v (%T)",
		field, value, value,
	)
}

var _ permissions.AssignmentPolicyEligibilityProjectionProvider = assignmentPolicyEligibilityProjectionProvider{}
