package sfcrmappservice

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"sort"
	"strings"

	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	searchsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/searchv1"
)

// Both scope endpoints assemble the installed declarations and effective
// object shape from one view. AppRelease defaults cannot substitute for the
// effective L1+L2 configuration, even when their component names look familiar.
func (client *RuntimePermissionMetadataClient) readInstalledSearchScope(ctx context.Context) (catalogsemantic.AppMetadataViewV3, map[string]searchObjectSpec, []string, error) {
	if client == nil {
		return catalogsemantic.AppMetadataViewV3{}, nil, nil, unavailablePermissionMetadata()
	}
	view, specs, err := readObjectSpecs(ctx, client.appMetadata)
	if err != nil {
		return view, nil, nil, err
	}
	order, err := effectiveSearchScopeOrder(view, specs)
	return view, specs, order, err
}

type searchScopeComponentItem struct {
	Name  string `json:"name"`
	Label string `json:"label"`
}

func effectiveSearchScopeOrder(view catalogsemantic.AppMetadataViewV3, specs map[string]searchObjectSpec) ([]string, error) {
	var component *catalogsemantic.EffectiveMetadataComponent
	for index := range view.EffectiveComponents {
		candidate := &view.EffectiveComponents[index]
		if candidate.ComponentKind != catalogsemantic.EffectiveSearchScopeComponentKind ||
			candidate.BusinessKey != catalogsemantic.EffectiveSearchScopeComponentBusinessKey {
			continue
		}
		if component != nil {
			return nil, unavailablePermissionMetadata()
		}
		component = candidate
	}
	if component == nil || component.ComponentFormatVersion != 1 {
		return nil, unavailablePermissionMetadata()
	}
	var items []searchScopeComponentItem
	decoder := json.NewDecoder(bytes.NewReader(component.CanonicalBehavior))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&items); err != nil || items == nil {
		return nil, unavailablePermissionMetadata()
	}
	var extra any
	if err := decoder.Decode(&extra); err != io.EOF {
		return nil, unavailablePermissionMetadata()
	}
	order := make([]string, 0, len(items))
	seen := make(map[string]bool, len(items))
	for _, item := range items {
		name := strings.TrimSpace(item.Name)
		key := strings.ToLower(name)
		if !validSearchIdentifier(name) || name != item.Name || strings.TrimSpace(item.Label) == "" || item.Label != strings.TrimSpace(item.Label) || seen[key] {
			return nil, unavailablePermissionMetadata()
		}
		seen[key] = true
		spec, ok := specs[key]
		if !ok {
			return nil, unavailablePermissionMetadata()
		}
		if objectSearchable(spec) {
			order = append(order, spec.APIName)
		}
	}
	return order, nil
}

func (client *RuntimePermissionMetadataClient) curatedSearchScopeOrder(ctx context.Context, bearer string) ([]string, error) {
	view, _, order, err := client.readInstalledSearchScope(ctx)
	if err != nil {
		return nil, err
	}
	if len(order) == 0 {
		return []string{}, nil
	}
	resourceTypes := append([]string(nil), order...)
	sort.Strings(resourceTypes)
	permissions, err := readEffectivePermissionSelection(ctx, client.readEffective, bearer, "", view.Receipts.CatalogRevision,
		[]permissionsemantic.Section{permissionsemantic.SectionObjectPermissions}, resourceTypes)
	if err != nil {
		return nil, err
	}
	if err := validateSearchMetadataAuthority(ctx, client.reconcileAppMetadata, view, permissions.Receipts); err != nil {
		return nil, err
	}
	readable := make(map[string]bool, len(permissions.ObjectPermissions))
	for _, permission := range permissions.ObjectPermissions {
		readable[strings.ToLower(permission.ResourceType)] = permission.CanRead
	}
	result := make([]string, 0, len(order))
	for _, name := range order {
		if readable[strings.ToLower(name)] {
			result = append(result, name)
		}
	}
	return result, nil
}

type searchScopeOrderItemV63 struct {
	Type      string `json:"type"`
	URL       string `json:"url"`
	Name      string `json:"name"`
	Label     string `json:"label"`
	KeyPrefix string `json:"keyPrefix"`
}

func (client *RuntimePermissionMetadataClient) runtimeSearchScopeOrder(ctx context.Context, bearer, apiVersion string) ([]searchScopeOrderItemV63, error) {
	view, specs, order, err := client.readInstalledSearchScope(ctx)
	if err != nil {
		return nil, err
	}
	if len(order) == 0 {
		return []searchScopeOrderItemV63{}, nil
	}
	if client.readSearchScope == nil {
		return nil, unavailablePermissionMetadata()
	}
	revision := view.Receipts.CatalogRevision
	request := searchsemantic.ScopeOrderRequest{ObjectIDs: order, MinCatalogRevision: &revision}
	response, err := client.readSearchScope(ctx, bearer, request)
	if err != nil {
		return nil, err
	}
	if err := searchsemantic.ValidateScopeOrderResponse(request, response); err != nil {
		return nil, err
	}
	if err := validateSearchMetadataAuthority(ctx, client.reconcileAppMetadata, view, response.Receipts); err != nil {
		return nil, err
	}
	result := make([]searchScopeOrderItemV63, 0, len(response.ObjectIDs))
	for _, name := range response.ObjectIDs {
		spec, ok := specs[strings.ToLower(name)]
		if !ok {
			return nil, unavailablePermissionMetadata()
		}
		result = append(result, searchScopeOrderItemV63{
			Type: spec.APIName, Name: spec.APIName,
			URL:   fmt.Sprintf("/services/data/%s/sobjects/%s/describe", apiVersion, spec.APIName),
			Label: spec.Label, KeyPrefix: spec.KeyPrefix,
		})
	}
	return result, nil
}
