package app

import (
	"context"
	"encoding/json"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// AppInstallationView is the app installation shape expected by the web app client.

// ListAppsForUser keeps the parent app workflow boundary for workspace
// authorization, but the returned catalog shape is owned by registry.
func (store *Store) ListAppsForUser(ctx context.Context, userID string, workspaceID string) ([]registry.AppDefinitionView, error) {
	workspaceMember, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return nil, err
	}
	apps, err := store.registryStore().ListApps(ctx, workspaceID)
	if err != nil {
		return nil, err
	}
	hidden, err := store.hiddenBuiltinAppIdentities(ctx, workspaceID)
	if err != nil {
		return nil, err
	}
	visible := make([]registry.AppDefinitionView, 0, len(apps))
	for _, app := range apps {
		if hidden.contains(app.ID, app.Slug) {
			continue
		}
		visible = append(visible, app)
	}
	return store.registryStore().FilterDefinitionsForMember(
		ctx,
		workspaceID,
		workspaceMember.Member.ID,
		access.HasWorkspaceAdminRole(domain.WorkspaceRole(workspaceMember.WorkspaceRole)),
		visible,
	)
}

func (store *Store) ListAppInstallationsForUser(ctx context.Context, userID string, workspaceID string) ([]AppInstallationView, error) {
	workspaceMember, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return nil, err
	}
	installations, err := store.installationsFromServiceActors(ctx, workspaceID)
	if err != nil {
		return nil, err
	}
	activeInstallations := make([]AppInstallationView, 0, len(installations))
	for _, installation := range installations {
		if installation.Enabled {
			activeInstallations = append(activeInstallations, installation)
		}
	}
	activeInstallations, err = store.filterAppInstallationsForWorkspaceMember(
		ctx,
		workspaceID,
		workspaceMember,
		activeInstallations,
	)
	if err != nil {
		return nil, err
	}
	activeInstallations, err = store.filterPersonalInstallationsForViewer(
		ctx,
		workspaceID,
		strings.TrimSpace(workspaceMember.Member.ID),
		activeInstallations,
	)
	if err != nil {
		return nil, err
	}
	for idx := range activeInstallations {
		// Manifest configuration for ordinary Apps is workspace-visible so every
		// member can start an approval request from the active snapshot. Provider
		// credentials stay on their dedicated owner-only APIs and are never
		// projected to the browser.
		if isProviderAppInstallation(activeInstallations[idx]) {
			activeInstallations[idx].Config = nil
		}
	}
	return activeInstallations, nil
}

func isProviderAppInstallation(installation AppInstallationView) bool {
	switch strings.ToLower(strings.TrimSpace(installation.AppSlug)) {
	case "github", "gitea":
		return true
	default:
		return false
	}
}

func (store *Store) requireWorkspaceServiceInstall(ctx context.Context, userID string, workspaceID string) error {
	_, err := store.loadAuthorizedHumanWorkspaceMember(ctx, store.db, userID, workspaceID, workspaceInstallServiceAction)
	return err
}

type hiddenAppIdentities struct {
	ids   map[string]struct{}
	slugs map[string]struct{}
}

func newHiddenAppIdentities() hiddenAppIdentities {
	return hiddenAppIdentities{
		ids:   map[string]struct{}{},
		slugs: map[string]struct{}{},
	}
}

func (identities hiddenAppIdentities) add(id string, slug string) {
	if id = strings.TrimSpace(id); id != "" {
		identities.ids[id] = struct{}{}
	}
	if slug = strings.TrimSpace(slug); slug != "" {
		identities.slugs[slug] = struct{}{}
	}
}

func (identities hiddenAppIdentities) contains(id string, slug string) bool {
	if _, ok := identities.ids[strings.TrimSpace(id)]; ok {
		return true
	}
	_, ok := identities.slugs[strings.TrimSpace(slug)]
	return ok
}

func (store *Store) hiddenBuiltinAppIdentities(ctx context.Context, workspaceID string) (hiddenAppIdentities, error) {
	hidden := newHiddenAppIdentities()
	definitions, err := store.serviceActorStore().ListDefinitions(ctx, serviceactor.KindApp)
	if err != nil {
		return hidden, err
	}
	for _, definition := range definitions {
		if serviceDefinitionUsesInternalHandler(definition.Config) {
			hidden.add(definition.ID, definition.Slug)
		}
	}
	summaries, err := store.serviceActorStore().ListInstallationSummaries(ctx, workspaceID, serviceactor.KindApp)
	if err != nil {
		return hidden, err
	}
	for _, summary := range summaries {
		if serviceInstallationIsBuiltin(summary.Installation.Metadata) {
			hidden.add(summary.Definition.ID, summary.Definition.Slug)
		}
	}
	return hidden, nil
}

func serviceDefinitionUsesInternalHandler(config []byte) bool {
	if len(config) == 0 {
		return false
	}
	var value struct {
		ExecutionTransport string `json:"executionTransport"`
	}
	return json.Unmarshal(config, &value) == nil &&
		strings.TrimSpace(value.ExecutionTransport) == serviceactor.RuntimeTransportKindInternalHandler
}

func serviceActorSummaryVisibleInAppManagement(summary serviceactor.InstallationSummary) bool {
	return !serviceInstallationIsBuiltin(summary.Installation.Metadata) &&
		!serviceDefinitionUsesInternalHandler(summary.Definition.Config)
}
