package app

import (
	"context"
	"errors"
	"maps"
	"net/http"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
	storeapp "github.com/tidbcloud/AgentTeam/internal/store/app"
)

func (handler *WorkspaceAppHandler) ExtensionProviderConnectionGet(
	r api.WorkspaceScopedRequest,
) (api.Response, error) {
	provider, err := handler.resolveExtensionProvider(r.Context(), r.WorkspaceID(), r.Gin.Param("installationId"))
	if err != nil {
		return api.Response{}, err
	}
	view, err := handler.store.GetExtensionProviderConnectionForUser(
		r.Context(),
		r.UserID(),
		r.WorkspaceID(),
		strings.TrimSpace(r.Gin.Param("installationId")),
		provider.Target,
	)
	if err != nil {
		return api.Response{}, err
	}
	var credentials map[string]string
	if view.Active && view.CredentialSet {
		credentials, _, err = handler.store.ResolveExtensionProviderRuntimeConnection(
			r.Context(),
			r.WorkspaceID(),
			strings.TrimSpace(r.Gin.Param("installationId")),
			provider.Target,
			provider.ConfigContract,
		)
		if err != nil {
			return api.Response{}, err
		}
	}
	return noStoreOK(converter.ToExtensionProviderConnectionResponse(
		view,
		provider.Auth.Fields,
		credentials,
	)), nil
}

func (handler *WorkspaceAppHandler) ExtensionProviderConnectionPut(
	r api.WorkspaceScopedJSON[dto.ExtensionProviderConnectionPutRequest],
) (api.Response, error) {
	provider, err := handler.resolveExtensionProvider(r.Context(), r.WorkspaceID(), r.Gin.Param("installationId"))
	if err != nil {
		return api.Response{}, err
	}
	if err := handler.store.AuthorizeExtensionProviderConnectionAdmin(
		r.Context(),
		r.UserID(),
		r.WorkspaceID(),
		strings.TrimSpace(r.Gin.Param("installationId")),
		provider.Target,
	); err != nil {
		return api.Response{}, err
	}
	credentials := runner.Credentials{}
	for name, value := range r.Body.Credentials {
		credentials[strings.TrimSpace(name)] = strings.TrimSpace(value)
	}
	missingSecret := false
	for _, field := range provider.Auth.Fields {
		if field.Secret && credentials[field.Name] == "" {
			missingSecret = true
			break
		}
	}
	if missingSecret {
		storedCredentials, _, resolveErr :=
			handler.store.ResolveExtensionProviderRuntimeConnection(
				r.Context(),
				r.WorkspaceID(),
				strings.TrimSpace(r.Gin.Param("installationId")),
				provider.Target,
				provider.ConfigContract,
			)
		if resolveErr != nil && !errors.Is(resolveErr, storeapp.ErrNotFound) {
			return api.Response{}, resolveErr
		}
		nonSecretValuesUnchanged := true
		for _, field := range provider.Auth.Fields {
			if !field.Secret &&
				credentials[field.Name] != storedCredentials[field.Name] {
				nonSecretValuesUnchanged = false
				break
			}
		}
		if nonSecretValuesUnchanged {
			for _, field := range provider.Auth.Fields {
				if field.Secret && credentials[field.Name] == "" {
					if stored := storedCredentials[field.Name]; stored != "" {
						credentials[field.Name] = stored
					}
				}
			}
		}
	}
	if err := provider.Auth.Validate(credentials); err != nil {
		return api.Response{}, extensionProviderCredentialHTTPError(
			runner.Errorf(runner.ErrorValidation, "credentials do not match the provider contract", err))
	}
	config, err := provider.PrepareConnection(r.Context(), credentials)
	if err != nil {
		return api.Response{}, extensionProviderCredentialHTTPError(err)
	}
	if err := provider.ConfigContract.Validate(config); err != nil {
		return api.Response{}, extensionProviderCredentialHTTPError(
			runner.Errorf(
				runner.ErrorValidation,
				"provider connection config is invalid",
				nil,
			),
		)
	}
	view, err := handler.store.UpsertExtensionProviderConnectionForUser(
		r.Context(),
		r.UserID(),
		r.WorkspaceID(),
		strings.TrimSpace(r.Gin.Param("installationId")),
		provider.Target,
		credentials,
		config,
		r.Body.ExpectedUpdatedAt,
	)
	if err != nil {
		return api.Response{}, err
	}
	handler.emitWorkspaceAppsUpdated(r.WorkspaceID())
	return noStoreOK(converter.ToExtensionProviderConnectionResponse(
		view,
		provider.Auth.Fields,
		credentials,
	)), nil
}

func (handler *WorkspaceAppHandler) ExtensionProviderConnectionDelete(
	r api.WorkspaceScopedRequest,
) (api.Response, error) {
	provider, err := handler.resolveExtensionProvider(r.Context(), r.WorkspaceID(), r.Gin.Param("installationId"))
	if err != nil {
		return api.Response{}, err
	}
	if err := handler.store.DeleteExtensionProviderConnectionForUser(
		r.Context(),
		r.UserID(),
		r.WorkspaceID(),
		strings.TrimSpace(r.Gin.Param("installationId")),
		provider.Target,
	); err != nil {
		return api.Response{}, err
	}
	handler.emitWorkspaceAppsUpdated(r.WorkspaceID())
	return noStoreOK(converter.ToExtensionProviderConnectionResponse(
		storeapp.ExtensionProviderConnectionView{Active: false, Slug: provider.Target.AppSlug},
		provider.Auth.Fields,
		nil,
	)), nil
}

func (handler *WorkspaceAppHandler) ExtensionProviderConnectionTest(
	r api.WorkspaceScopedRequest,
) (api.Response, error) {
	provider, err := handler.resolveExtensionProvider(r.Context(), r.WorkspaceID(), r.Gin.Param("installationId"))
	if err != nil {
		return api.Response{}, err
	}
	if handler.extensionProviders == nil {
		return api.Response{}, api.NewHTTPError(
			http.StatusServiceUnavailable,
			"Extension provider connection testing is unavailable",
		)
	}
	installationID := strings.TrimSpace(r.Gin.Param("installationId"))
	if err := handler.store.AuthorizeExtensionProviderConnectionAdmin(
		r.Context(),
		r.UserID(),
		r.WorkspaceID(),
		installationID,
		provider.Target,
	); err != nil {
		return api.Response{}, err
	}
	credentials, storedConfig, err :=
		handler.store.ResolveExtensionProviderRuntimeConnection(
			r.Context(),
			r.WorkspaceID(),
			installationID,
			provider.Target,
			provider.ConfigContract,
		)
	if err != nil {
		return api.Response{}, err
	}
	config, err := provider.PrepareConnection(
		r.Context(),
		runner.Credentials(credentials),
	)
	if err != nil {
		return api.Response{}, extensionProviderCredentialHTTPError(err)
	}
	if err := provider.ConfigContract.Validate(config); err != nil {
		return api.Response{}, extensionProviderCredentialHTTPError(
			runner.Errorf(
				runner.ErrorValidation,
				"provider connection config is invalid",
				nil,
			),
		)
	}
	if !maps.Equal(config, storedConfig) {
		return api.Response{}, api.NewHTTPError(
			http.StatusConflict,
			"Provider connection topology changed. Reconnect to update it.",
		)
	}
	return noStoreOK(dto.ExtensionProviderConnectionTestResponse{OK: true}), nil
}

// resolveExtensionProvider resolves the trusted provider behind one
// installation and fails closed when the App ships no provider Extension.
func (handler *WorkspaceAppHandler) resolveExtensionProvider(
	ctx context.Context,
	workspaceID string,
	installationID string,
) (ResolvedExtensionProvider, error) {
	if handler == nil || handler.extensionProviders == nil {
		return ResolvedExtensionProvider{}, api.NewHTTPError(
			http.StatusServiceUnavailable,
			"Extension provider connections are unavailable",
		)
	}
	return handler.extensionProviders.ResolveProviderForInstallation(
		ctx,
		workspaceID,
		strings.TrimSpace(installationID),
	)
}

// extensionProviderCredentialHTTPError maps the closed provider validation
// classification onto the browser-facing status set. The rendered message is
// stable and never carries upstream detail.
func extensionProviderCredentialHTTPError(err error) error {
	switch runner.CodeOf(err) {
	case runner.ErrorValidation,
		runner.ErrorConnectionUnavailable,
		runner.ErrorProviderAuthUnavailable:
		return api.NewHTTPError(
			http.StatusBadRequest,
			"Provider rejected the connection credentials",
		)
	case runner.ErrorRateLimited:
		return api.NewHTTPError(
			http.StatusTooManyRequests,
			"Provider rate limited the credential check",
		)
	default:
		return api.NewHTTPError(
			http.StatusBadGateway,
			"Provider credential check failed",
		)
	}
}
