package sfcrmappservice

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	mutationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recyclebinmutationv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recyclebinv1"
)

type RuntimeRecycleBinFunc func(context.Context, string, semantic.Request) (semantic.Response, error)
type RuntimeRecycleBinMutationFunc func(context.Context, string, mutationsemantic.Request) (mutationsemantic.Response, error)

type recycleBinMutationOperation uint8

const (
	recycleBinMutationRestore recycleBinMutationOperation = iota + 1
	recycleBinMutationPurge
)

func matchRecycleBinMutationRoute(escapedPath string) (recycleBinMutationOperation, string, bool) {
	for base := range supportedRecycleBinPaths {
		for _, candidate := range []struct {
			prefix    string
			operation recycleBinMutationOperation
		}{
			{prefix: base + "/restore/", operation: recycleBinMutationRestore},
			{prefix: base + "/", operation: recycleBinMutationPurge},
		} {
			if !strings.HasPrefix(escapedPath, candidate.prefix) {
				continue
			}
			escapedID := strings.TrimPrefix(escapedPath, candidate.prefix)
			if escapedID == "" || strings.Contains(escapedID, "/") {
				return 0, "", false
			}
			recordID, err := url.PathUnescape(escapedID)
			if err != nil || strings.Contains(recordID, "/") || recordID == "" {
				return 0, "", false
			}
			return candidate.operation, recordID, true
		}
	}
	return 0, "", false
}

func mutateRecycleBin(ctx context.Context, runtime RuntimeRecycleBinMutationFunc, bearer, recordID string) error {
	if runtime == nil {
		return &mutationsemantic.Failure{Category: mutationsemantic.FailureUnavailable}
	}
	request := mutationsemantic.Request{RecordID: recordID}
	if err := mutationsemantic.ValidateRequest(ctx, request); err != nil {
		return err
	}
	response, err := runtime(ctx, bearer, request)
	if err != nil {
		return err
	}
	return mutationsemantic.ValidateResponse(request, response)
}

func listRecycleBin(ctx context.Context, runtime RuntimeRecycleBinFunc, bearer string, scope semantic.Scope) (crmwire.RecycleBinListResponse, error) {
	if runtime == nil {
		return crmwire.RecycleBinListResponse{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	request := semantic.Request{Scope: scope}
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return crmwire.RecycleBinListResponse{}, err
	}
	response, err := runtime(ctx, bearer, request)
	if err != nil {
		return crmwire.RecycleBinListResponse{}, err
	}
	if err := semantic.ValidateResponse(response); err != nil {
		return crmwire.RecycleBinListResponse{}, err
	}
	items := make([]crmwire.RecycleBinItem, 0, len(response.Items))
	for _, item := range response.Items {
		items = append(items, crmwire.RecycleBinItem{
			ID: item.RecordID, RecordID: item.RecordID, RecordName: item.RecordName,
			ObjectAPIName: item.ObjectID, DeletedBy: item.DeletedBy,
			DeletedDate: item.DeletedDate, CreatedDate: item.CreatedDate,
			LastModifiedDate: item.LastModifiedDate,
		})
	}
	failures := make([]crmwire.RecycleBinFailure, 0, len(response.FailedObjects))
	for _, failure := range response.FailedObjects {
		failures = append(failures, crmwire.RecycleBinFailure{
			ObjectAPIName: failure.ObjectID,
			ErrorCode:     legacyRecycleBinFailureCode(failure.Code),
			Message:       failure.Message,
		})
	}
	return crmwire.RecycleBinListResponse{
		Items: items, TotalSize: len(items), PartialFailure: len(failures) > 0,
		FailedObjects: failures,
	}, nil
}

func recycleBinScope(value string) (semantic.Scope, error) {
	switch value {
	case "", "mine":
		return semantic.ScopeMine, nil
	case "all":
		return semantic.ScopeAll, nil
	default:
		return "", fmt.Errorf("unsupported scope %q (expected %q or %q)", value, "mine", "all")
	}
}

func legacyRecycleBinFailureCode(code semantic.ObjectFailureCode) string {
	switch code {
	case semantic.ObjectFailureForbidden:
		return "forbidden"
	case semantic.ObjectFailureInvalid:
		return "invalid_request"
	case semantic.ObjectFailureInternal:
		return "internal"
	default:
		return "unknown"
	}
}

func writeRecycleBinRuntimeFailure(writer http.ResponseWriter, scope semantic.Scope, err error) {
	var failure *semantic.Failure
	if scope == semantic.ScopeAll && errors.As(err, &failure) && failure.Category == semantic.FailureForbidden {
		// Preserve the current Salesforce route's public scope=all denial while
		// keeping the Runtime failure itself neutral and non-disclosing.
		writeCRMErrorWith(writer, http.StatusForbidden, crmwire.SFCodeForbidden, "ViewAllData or ModifyAllData required to view the org recycle bin", writeLegacyJSON)
		return
	}
	writeRuntimeFailureWith(writer, err, writeLegacyJSON)
}
