package server

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api"
	appapi "github.com/tidbcloud/AgentTeam/internal/api/app"
	"github.com/tidbcloud/AgentTeam/internal/extension"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// extensionProviderIndex adapts the trusted provider registry onto the App
// connection API: it resolves the App installation's definition onto the
// provider Extension that ships with the binary.
type extensionProviderIndex struct {
	store   *app.Store
	byAppID map[string]extension.Extension
}

func newExtensionProviderIndex(
	store *app.Store,
	deps providers.Deps,
) (*extensionProviderIndex, error) {
	if store == nil {
		return nil, fmt.Errorf("extension provider index requires the App store")
	}
	byAppID, err := providers.ExtensionsByAppID(deps)
	if err != nil {
		return nil, err
	}
	return &extensionProviderIndex{store: store, byAppID: byAppID}, nil
}

// ProviderConnectionCapabilityForAppID projects only an explicit server-side
// generic connection opt-in. The domain is derived from the registered
// Definition and cannot be supplied by an App record or browser payload.
func (index *extensionProviderIndex) ProviderConnectionCapabilityForAppID(
	appID string,
) (appapi.ProviderConnectionCapability, bool) {
	if index == nil {
		return appapi.ProviderConnectionCapability{}, false
	}
	registered, ok := index.byAppID[strings.TrimSpace(appID)]
	if !ok || !registered.Distribution.GenericProviderConnection {
		return appapi.ProviderConnectionCapability{}, false
	}
	domain := strings.TrimSpace(registered.Definition.Domain)
	if domain == "" {
		return appapi.ProviderConnectionCapability{}, false
	}
	return appapi.ProviderConnectionCapability{Domain: domain}, true
}

// ResolveProviderForInstallation maps one App installation onto its provider
// connection contract. Apps without a provider Extension resolve to 404.
func (index *extensionProviderIndex) ResolveProviderForInstallation(
	ctx context.Context,
	workspaceID string,
	installationID string,
) (appapi.ResolvedExtensionProvider, error) {
	appID, err := index.store.ExtensionProviderAppIDForInstallation(
		ctx,
		workspaceID,
		installationID,
	)
	if err != nil {
		if errors.Is(err, serviceactor.ErrNotFound) {
			return appapi.ResolvedExtensionProvider{}, api.ErrNotFound
		}
		return appapi.ResolvedExtensionProvider{}, err
	}
	return index.resolveRegisteredProvider(appID)
}

func (index *extensionProviderIndex) resolveRegisteredProvider(
	appID string,
) (appapi.ResolvedExtensionProvider, error) {
	extension, ok := index.byAppID[appID]
	if !ok ||
		!extension.Distribution.GenericProviderConnection ||
		extension.Implementation.Auth.Kind == runner.AuthNone {
		// Legacy Builtins manage credentials through their bespoke flows and
		// are not served by the generic connection API.
		return appapi.ResolvedExtensionProvider{}, api.ErrNotFound
	}
	hasValidator := extension.Implementation.ValidateCredentials != nil
	hasPreparer := extension.Implementation.PrepareConnection != nil
	if hasValidator == hasPreparer {
		return appapi.ResolvedExtensionProvider{}, api.ErrNotFound
	}
	prepare := extension.Implementation.PrepareConnection
	if prepare == nil {
		validator := extension.Implementation.ValidateCredentials
		prepare = func(
			ctx context.Context,
			credentials runner.Credentials,
		) (runner.ConnectionConfig, error) {
			if err := validator(ctx, credentials); err != nil {
				return nil, err
			}
			return nil, nil
		}
	}
	return appapi.ResolvedExtensionProvider{
		Target: app.ExtensionProviderTarget{
			AppID:   extension.Distribution.AppID,
			AppSlug: extension.Distribution.AppSlug,
			Label:   extension.Definition.Name,
		},
		Auth:              extension.Implementation.Auth,
		PrepareConnection: prepare,
		ConfigContract:    extension.Implementation.ConnectionConfig,
	}, nil
}
