package registry

import (
	"context"
	"fmt"
	"strings"
)

const (
	CatalogVisibilityPrivate = "private"
	CatalogVisibilityPublic  = "public"
)

// NormalizeCatalogVisibility validates a workspace App catalog audience value.
func NormalizeCatalogVisibility(value string, defaultPrivate bool) (string, error) {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case CatalogVisibilityPrivate:
		return CatalogVisibilityPrivate, nil
	case CatalogVisibilityPublic:
		return CatalogVisibilityPublic, nil
	case "":
		if defaultPrivate {
			return CatalogVisibilityPrivate, nil
		}
	}
	return "", badRequest("catalogVisibility must be private or public")
}

// CatalogAudience is one workspace-attributed App's catalog visibility and the
// requested member's independent active management capability.
type CatalogAudience struct {
	CatalogVisibility string
	CanManage         bool
}

// LoadCatalogAudienceForMember returns the bounded workspace catalog audience
// needed to project App definitions or installations for one member.
func (store *Store) LoadCatalogAudienceForMember(
	ctx context.Context,
	workspaceID string,
	memberID string,
) (map[string]CatalogAudience, error) {
	rows, err := store.db.Query(ctx, `
SELECT attribution.service_definition_id,
       attribution.catalog_visibility,
       EXISTS (
           SELECT 1
           FROM extension_definition_managers manager
           WHERE manager.workspace_id = attribution.workspace_id
             AND manager.service_definition_id = attribution.service_definition_id
             AND manager.member_id = $2
             AND manager.status = 'active'
             AND manager.ended_at IS NULL
       ) AS can_manage
FROM extension_catalog_attributions attribution
WHERE attribution.workspace_id = $1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(memberID))
	if err != nil {
		return nil, fmt.Errorf("list workspace App manager visibility: %w", err)
	}
	defer rows.Close()

	audienceByAppID := make(map[string]CatalogAudience)
	for rows.Next() {
		var appID, catalogVisibility string
		var canManage bool
		if err := rows.Scan(&appID, &catalogVisibility, &canManage); err != nil {
			return nil, fmt.Errorf("scan workspace App manager visibility: %w", err)
		}
		if appID = strings.TrimSpace(appID); appID != "" {
			catalogVisibility = strings.ToLower(strings.TrimSpace(catalogVisibility))
			if catalogVisibility != CatalogVisibilityPrivate && catalogVisibility != CatalogVisibilityPublic {
				return nil, fmt.Errorf("scan workspace App manager visibility: invalid catalog visibility %q", catalogVisibility)
			}
			audienceByAppID[appID] = CatalogAudience{
				CatalogVisibility: catalogVisibility,
				CanManage:         canManage,
			}
		}
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate workspace App manager visibility: %w", err)
	}
	return audienceByAppID, nil
}

// FilterDefinitionsForMember applies workspace catalog audience rules to
// registry-owned App definition views.
func (store *Store) FilterDefinitionsForMember(
	ctx context.Context,
	workspaceID string,
	memberID string,
	isWorkspaceAdmin bool,
	definitions []AppDefinitionView,
) ([]AppDefinitionView, error) {
	audienceByAppID, err := store.LoadCatalogAudienceForMember(ctx, workspaceID, memberID)
	if err != nil {
		return nil, err
	}
	visible := make([]AppDefinitionView, 0, len(definitions))
	for _, definition := range definitions {
		audience, attributed := audienceByAppID[strings.TrimSpace(definition.ID)]
		attributedExternal := attributed && !IsProductApp(definition.ID, definition.Slug)
		if attributedExternal {
			definition.CatalogVisibility = audience.CatalogVisibility
			// Public is an audience choice, never management authority. Active App
			// managers and workspace administrators retain private visibility.
			if !isWorkspaceAdmin && !audience.CanManage &&
				audience.CatalogVisibility != CatalogVisibilityPublic {
				continue
			}
			if !isWorkspaceAdmin && !audience.CanManage {
				definition.LatestScopes = nil
				definition.LatestConfigSchema = ""
				definition.RuntimeCapability = nil
			}
		}
		if !attributedExternal && isWorkspaceDefinition(definition) {
			continue
		}
		visible = append(visible, definition)
	}
	return visible, nil
}

func isWorkspaceDefinition(definition AppDefinitionView) bool {
	return !IsProductApp(definition.ID, definition.Slug) &&
		definition.RuntimeCapability != nil && definition.RuntimeCapability.Supported
}
