package sfcrmappservice

import (
	"context"
	"errors"
	"strings"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
)

// RuntimeEvaluateFunc is one complete neutral record-access use-case call. The
// concrete production owner performs Identity exchange before the Records RPC.
type RuntimeEvaluateFunc func(context.Context, string, semantic.Request) (semantic.Response, error)

// RecordAccess is the CRM product projection of one neutral Runtime result.
type RecordAccess struct {
	HasReadAccess   bool `json:"hasReadAccess"`
	HasEditAccess   bool `json:"hasEditAccess"`
	HasDeleteAccess bool `json:"hasDeleteAccess"`
	IsLocked        bool `json:"isLocked"`
}

// RuntimeRecordAccessClient owns only CRM request normalization and product
// projection. Runtime owns type resolution and every CRUD/RLS/lock decision.
type RuntimeRecordAccessClient struct {
	evaluate RuntimeEvaluateFunc
}

func NewRuntimeRecordAccessClient(evaluate RuntimeEvaluateFunc) (*RuntimeRecordAccessClient, error) {
	if evaluate == nil {
		return nil, errors.New("sfcrm AppService: Runtime evaluator is required")
	}
	return &RuntimeRecordAccessClient{evaluate: evaluate}, nil
}

// Evaluate makes zero Runtime calls for empty or locally invalid input and one
// coarse call for every accepted non-empty request.
func (c *RuntimeRecordAccessClient) Evaluate(ctx context.Context, bearer, objectAPIName string, recordIDs []string) (map[string]RecordAccess, semantic.Receipts, error) {
	if len(recordIDs) == 0 {
		return map[string]RecordAccess{}, semantic.Receipts{}, nil
	}
	request := semantic.Request{RecordIDs: append([]string(nil), recordIDs...)}
	object := strings.ToLower(strings.TrimSpace(objectAPIName))
	if object != "" {
		request.ObjectID = &object
	}
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, semantic.Receipts{}, err
	}
	if object == "" {
		request.RecordIDs = normalizedUntypedRecordIDs(recordIDs)
		if len(request.RecordIDs) == 0 {
			return map[string]RecordAccess{}, semantic.Receipts{}, nil
		}
	}
	if c == nil || c.evaluate == nil {
		return nil, semantic.Receipts{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	response, err := c.evaluate(ctx, bearer, request)
	if err != nil {
		return nil, semantic.Receipts{}, err
	}
	if err := semantic.ValidateResponse(request, response); err != nil {
		return nil, semantic.Receipts{}, err
	}
	result := make(map[string]RecordAccess, len(response.Results))
	for _, entry := range response.Results {
		result[entry.RecordID] = RecordAccess{
			HasReadAccess: entry.Access.Read, HasEditAccess: entry.Access.Update,
			HasDeleteAccess: entry.Access.Delete, IsLocked: entry.Access.Locked,
		}
	}
	return result, response.Receipts, nil
}

func normalizedUntypedRecordIDs(recordIDs []string) []string {
	result := make([]string, 0, len(recordIDs))
	seen := make(map[string]struct{}, len(recordIDs))
	for _, raw := range recordIDs {
		id := strings.TrimSpace(raw)
		if id == "" {
			continue
		}
		if _, duplicate := seen[id]; duplicate {
			continue
		}
		seen[id] = struct{}{}
		result = append(result, id)
	}
	return result
}
