package converter

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
	"github.com/tidbcloud/AgentTeam/internal/service/app/installation"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/app/runtime"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// --- Registry types ---

func ToAppDefinitionViews(items []registry.AppDefinitionView) []dto.AppDefinitionResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.AppDefinitionResponse, 0, len(items))
	for _, item := range items {
		out = append(out, ToAppDefinitionView(item))
	}
	return out
}

func ToAppDefinitionView(v registry.AppDefinitionView) dto.AppDefinitionResponse {
	return dto.AppDefinitionResponse{
		ID:                v.ID,
		Slug:              v.Slug,
		Name:              v.Name,
		Description:       v.Description,
		AvatarURL:         v.AvatarURL,
		Kind:              v.Kind,
		Visibility:        v.Visibility,
		CatalogVisibility: v.CatalogVisibility,
		RuntimeCapability: ToAppRuntimeCapabilityResponse(v.RuntimeCapability),
		CreatedAt:         v.CreatedAt,
		UpdatedAt:         v.UpdatedAt,
	}
}

func ToAppRuntimeCapabilityResponse(v *registry.AppRuntimeCapabilityView) *dto.AppRuntimeCapabilityResponse {
	if v == nil || !v.Supported {
		return nil
	}
	return &dto.AppRuntimeCapabilityResponse{
		Supported:  true,
		Transports: append([]string(nil), v.Transports...),
	}
}

// --- Connection ---

func ToRuntimeConnectionResponse(v runtime.ConnectionView) dto.AppConnectionResponse {
	return dto.AppConnectionResponse{
		ID:                 v.ID,
		ExternalSubjectRef: v.ExternalSubjectRef,
		DisplayLabel:       v.DisplayLabel,
		RequiresUpgrade:    v.RequiresUpgrade,
		MissingPermissions: v.MissingPermissions,
		MissingEvents:      v.MissingEvents,
		Enabled:            v.Enabled,
		CreatedAt:          v.CreatedAt,
		UpdatedAt:          v.UpdatedAt,
	}
}

func ToRuntimeConnectionResponses(items []runtime.ConnectionView) []dto.AppConnectionResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.AppConnectionResponse, 0, len(items))
	for _, item := range items {
		out = append(out, ToRuntimeConnectionResponse(item))
	}
	return out
}

// --- Installation ---

func ToAppInstallationResponse(v app.AppInstallationView) dto.AppInstallationResponse {
	return dto.AppInstallationResponse{
		ID:                v.ID,
		AppID:             v.AppID,
		AppSlug:           v.AppSlug,
		AppName:           v.AppName,
		AvatarURL:         v.AvatarURL,
		InstanceKey:       v.InstanceKey,
		OwnershipKind:     v.OwnershipKind,
		MemberID:          v.MemberID,
		Handle:            v.Handle,
		Connections:       ToRuntimeConnectionResponses(v.Connections),
		RuntimeCapability: ToAppRuntimeCapabilityResponse(v.RuntimeCapability),
		CatalogVisibility: v.CatalogVisibility,
		CanManageApp:      v.CanManageApp,
		Config:            v.Config,
		Enabled:           v.Enabled,
		CreatedAt:         v.CreatedAt,
		UpdatedAt:         v.UpdatedAt,
		InChannel:         v.InChannel,
	}
}

func ToInstallationAccessGrantResponse(v serviceactor.PrincipalAuthorization) dto.InstallationAccessGrantResponse {
	scopes := []string{}
	var decoded []string
	if len(v.ScopesJSON) > 0 {
		_ = json.Unmarshal(v.ScopesJSON, &decoded)
	}
	for _, scope := range decoded {
		if trimmed := strings.TrimSpace(scope); trimmed != "" {
			scopes = append(scopes, trimmed)
		}
	}
	accessRole := v.AccessRole
	if accessRole == "" {
		accessRole = serviceactor.AccessRoleUse
	}
	return dto.InstallationAccessGrantResponse{
		ID:            v.ID,
		PrincipalKind: v.PrincipalKind,
		PrincipalRef:  v.PrincipalRef,
		AccessRole:    accessRole,
		Status:        v.Status,
		Scopes:        scopes,
		CreatedAt:     v.CreatedAt,
		UpdatedAt:     v.UpdatedAt,
	}
}

func ToInstallationAccessGrantResponses(items []serviceactor.PrincipalAuthorization) []dto.InstallationAccessGrantResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.InstallationAccessGrantResponse, 0, len(items))
	for _, item := range items {
		out = append(out, ToInstallationAccessGrantResponse(item))
	}
	return out
}

func ToAppVisibilityUpdateResponse(v app.AppVisibilityUpdateResult) dto.AppVisibilityUpdateResponse {
	return dto.AppVisibilityUpdateResponse{
		CatalogVisibility: v.CatalogVisibility,
	}
}

func ToAppPermissionsResponse(v app.AppPermissionsView) dto.WorkspaceAppPermissionsResponse {
	allowedScopes := v.AllowedScopes
	if allowedScopes == nil {
		allowedScopes = []string{}
	}
	activeScopes := v.ActiveScopes
	if activeScopes == nil {
		activeScopes = []string{}
	}
	return dto.WorkspaceAppPermissionsResponse{
		AllowedScopes: allowedScopes,
		ActiveScopes:  activeScopes,
		PolicyDigest:  v.PolicyDigest,
	}
}

func ToAppPermissionsUpdateResponse(v app.AppPermissionsUpdateResult) dto.WorkspaceAppPermissionsResponse {
	activeScopes := v.ActiveScopes
	if activeScopes == nil {
		activeScopes = []string{}
	}
	return dto.WorkspaceAppPermissionsResponse{
		ActiveScopes: activeScopes,
		PolicyDigest: v.PolicyDigest,
	}
}

func ToDirectAppInstallationResponse(v app.AppInstallationView) dto.DirectAppInstallationResponse {
	config := v.Config
	if config == nil {
		config = map[string]any{}
	}
	return dto.DirectAppInstallationResponse{
		AppInstallationResponse: ToAppInstallationResponse(v),
		Config:                  config,
	}
}

func ToAppInstallationResponses(items []app.AppInstallationView) []dto.AppInstallationResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.AppInstallationResponse, 0, len(items))
	for _, item := range items {
		out = append(out, ToAppInstallationResponse(item))
	}
	return out
}

func ToAppInstallationChannelResponses(items []app.AppInstallationChannelView) []dto.AppInstallationChannelResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.AppInstallationChannelResponse, 0, len(items))
	for _, item := range items {
		out = append(out, dto.AppInstallationChannelResponse{
			ID: item.ID, Slug: item.Slug, DisplayName: item.DisplayName,
			Description: item.Description, Visibility: item.Visibility,
			CanRemove: item.CanRemove,
		})
	}
	return out
}

func ToInstallationExtensionResponses(items []app.InstallationExtensionView) []dto.InstallationExtensionResponse {
	out := make([]dto.InstallationExtensionResponse, 0, len(items))
	for _, item := range items {
		response := dto.InstallationExtensionResponse{
			ID:            item.ID,
			ExtensionName: item.ExtensionName,
			Definition: dto.InstallationExtensionDefinitionResponse{
				Domain:         item.DefinitionDomain,
				Version:        item.DefinitionVersion,
				Digest:         item.DefinitionDigest,
				DisplayName:    item.DefinitionDisplayName,
				DefaultName:    item.DefaultName,
				DefaultEnabled: item.DefaultEnabled,
			},
			Selected:        item.Selected,
			Status:          item.Status,
			DisabledReason:  item.DisabledReason,
			SelectionSource: item.SelectionSource,
		}
		if !item.CreatedAt.IsZero() {
			createdAt := item.CreatedAt
			response.CreatedAt = &createdAt
		}
		if !item.UpdatedAt.IsZero() {
			updatedAt := item.UpdatedAt
			response.UpdatedAt = &updatedAt
		}
		out = append(out, response)
	}
	return out
}

func ToInstallationExtensionResponse(selection serviceactor.InstallationExtension) dto.InstallationExtensionResponse {
	createdAt := selection.CreatedAt
	updatedAt := selection.UpdatedAt
	return dto.InstallationExtensionResponse{
		ID:            selection.ID,
		ExtensionName: selection.ExtensionName,
		Definition: dto.InstallationExtensionDefinitionResponse{
			Domain:      selection.DefinitionDomain,
			Version:     selection.DefinitionVersion,
			Digest:      selection.DefinitionDigest,
			DisplayName: selection.DefinitionDomain,
		},
		Selected:        true,
		Status:          selection.Status,
		DisabledReason:  selection.DisabledReason,
		SelectionSource: selection.SelectionSource,
		CreatedAt:       &createdAt,
		UpdatedAt:       &updatedAt,
	}
}

func ToSearchEngineConnectionResponse(
	view app.SearchEngineConnectionView,
) dto.SearchEngineConnectionResponse {
	return dto.SearchEngineConnectionResponse{
		Provider: view.Provider,
		Active:   view.Active,
		Config: dto.SearchEngineConnectionConfigResponse{
			BaseURL:  view.Config.BaseURL,
			Language: view.Config.Language,
		},
		Credentials: dto.SearchEngineConnectionCredentialsResponse{
			APIKeyConfigured: view.APIKeyConfigured,
		},
		UpdatedAt: view.UpdatedAt,
	}
}

func ToAppInstallationRequestResponse(v app.AppInstallationRequestView) dto.AppInstallationRequestResponse {
	return dto.AppInstallationRequestResponse{
		ID:          v.ID,
		RequestKind: v.RequestKind,
		Status:      v.Status,
	}
}

func ToPendingAppInstallationRequestResponses(items []app.AppInstallationRequestView) ([]dto.PendingAppInstallationRequestResponse, error) {
	if items == nil {
		return nil, nil
	}
	out := make([]dto.PendingAppInstallationRequestResponse, 0, len(items))
	for _, item := range items {
		status := item.Status
		if status == app.AppInstallationRequestStatusSetupPatching {
			// setup_patching is an internal point-of-no-return lease, but from
			// the requester's browser it remains one in-progress request. Do not
			// expose it as terminal or unblock a second setup attempt.
			status = app.AppInstallationRequestStatusPending
		}
		pending := dto.PendingAppInstallationRequestResponse{
			ID:             item.ID,
			AppID:          item.AppID,
			AppSlug:        item.AppSlug,
			AppName:        item.AppName,
			RequestKind:    item.RequestKind,
			Status:         status,
			Reason:         item.RequestReason,
			InstallationID: item.InstallationID,
			CreatedAt:      item.CreatedAt,
		}
		if item.RequestKind == "share" {
			snapshot, err := decodeShareSnapshotForReview(item.RequestedConfig)
			if err != nil {
				return nil, err
			}
			pending.ReviewedPolicy = snapshot
		}
		out = append(out, pending)
	}
	return out, nil
}

// decodeShareSnapshotForReview strictly re-encodes the raw persisted
// snapshot map and decodes it through the service's fail-closed validator;
// corrupt or incomplete share snapshots surface as list errors instead of
// silently rendered partial policy.
func decodeShareSnapshotForReview(raw map[string]any) (*dto.InstallationSharePolicyResponse, error) {
	if raw == nil {
		return nil, nil
	}
	payload, err := json.Marshal(raw)
	if err != nil {
		return nil, fmt.Errorf("encode share snapshot for review: %w", err)
	}
	snapshot, err := installation.DecodeSharePolicySnapshot(payload)
	if err != nil {
		return nil, err
	}
	if snapshot == nil {
		return nil, nil
	}
	return toReviewedPolicy(snapshot), nil
}

func ToCustomAppRequestResponse(v app.CustomAppRequestResult) dto.CustomAppRequestResponse {
	return dto.CustomAppRequestResponse{
		ID:          v.Request.ID,
		RequestKind: v.Request.RequestKind,
		Status:      v.Request.Status,
	}
}

func ToCustomAppInstallationResponse(v app.CustomAppInstallationResult) dto.CustomAppInstallationResponse {
	return dto.CustomAppInstallationResponse{
		Installation:          ToDirectAppInstallationResponse(v.Installation),
		CallbackSigningSecret: v.CallbackSigningSecret,
		RunnerToken:           v.RunnerToken,
	}
}

func ToAppManagerResponse(v app.AppManagerAssignment) dto.AppManagerResponse {
	return dto.AppManagerResponse{
		ID:       v.ID,
		MemberID: v.MemberID,
		Status:   v.Status,
		Version:  v.Version,
	}
}

func ToAppManagerResponses(items []app.AppManagerAssignment) []dto.AppManagerResponse {
	out := make([]dto.AppManagerResponse, 0, len(items))
	for _, item := range items {
		out = append(out, ToAppManagerResponse(item))
	}
	return out
}

// --- Webhook delivery ---

func ToAppWebhookDeliveryResponse(v app.AppWebhookDeliveryView) dto.AppWebhookDeliveryResponse {
	return dto.AppWebhookDeliveryResponse{
		ID:                   v.ID,
		InstallationID:       v.InstallationID,
		AppID:                v.AppID,
		AppSlug:              v.AppSlug,
		AppName:              v.AppName,
		DeliveryKey:          v.DeliveryKey,
		EventType:            v.EventType,
		PayloadExcerpt:       v.PayloadExcerpt,
		MatchedSubscriptions: ToMatchedWebhookSubscriptionResponses(v.MatchedSubscriptions),
		Outcome:              v.Outcome,
		ProcessedAt:          v.ProcessedAt,
		ErrorMessage:         v.ErrorMessage,
		CreatedAt:            v.CreatedAt,
		UpdatedAt:            v.UpdatedAt,
	}
}

func ToAppWebhookDeliveryResponses(items []app.AppWebhookDeliveryView) []dto.AppWebhookDeliveryResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.AppWebhookDeliveryResponse, 0, len(items))
	for _, item := range items {
		out = append(out, ToAppWebhookDeliveryResponse(item))
	}
	return out
}

func ToMatchedWebhookSubscriptionResponses(items []app.MatchedWebhookSubscription) []dto.MatchedWebhookSubscriptionResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.MatchedWebhookSubscriptionResponse, 0, len(items))
	for _, item := range items {
		out = append(out, dto.MatchedWebhookSubscriptionResponse{
			ID:                 item.ID,
			ThreadID:           item.ThreadID,
			ResourceIdentifier: item.ResourceIdentifier,
		})
	}
	return out
}

// --- Agent app authorization ---

func ToAgentAppAuthorizationResponse(v app.AgentAppAuthorizationView) dto.AgentAppAuthorizationResponse {
	return dto.AgentAppAuthorizationResponse{
		AppID:                v.AppID,
		AppSlug:              v.AppSlug,
		AppName:              v.AppName,
		AvatarURL:            v.AvatarURL,
		AppInstallationID:    v.AppInstallationID,
		AppMemberID:          v.AppMemberID,
		Handle:               v.Handle,
		InstallationEnabled:  v.InstallationEnabled,
		Supported:            v.Supported,
		MCPName:              v.MCPName,
		MCPTools:             v.MCPTools,
		Scopes:               v.Scopes,
		AuthorizationID:      v.AuthorizationID,
		AuthorizationStatus:  v.AuthorizationStatus,
		DisabledReason:       v.DisabledReason,
		AuthorizedByMemberID: v.AuthorizedByMemberID,
		PolicyVersion:        v.PolicyVersion,
		AuthorizedAt:         v.AuthorizedAt,
		UpdatedAt:            v.UpdatedAt,
	}
}

func ToAgentAppAuthorizationResponses(items []app.AgentAppAuthorizationView) []dto.AgentAppAuthorizationResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.AgentAppAuthorizationResponse, 0, len(items))
	for _, item := range items {
		out = append(out, ToAgentAppAuthorizationResponse(item))
	}
	return out
}

// --- Service event subscriptions ---

func ToServiceEventSubscriptionResponse(v app.ServiceEventSubscriptionView) dto.ServiceEventSubscriptionResponse {
	return dto.ServiceEventSubscriptionResponse{
		ID:                     v.ID,
		AppInstallationID:      v.AppInstallationID,
		AppBindingID:           v.AppBindingID,
		ProviderConnectionRef:  v.ProviderConnectionRef,
		Provider:               v.Provider,
		TargetKind:             v.TargetKind,
		TargetRef:              v.TargetRef,
		DeliveryScopeID:        v.DeliveryScopeID,
		PermissionCheckScopeID: v.PermissionCheckScopeID,
		ResourceKind:           v.ResourceKind,
		ResourceRef:            v.ResourceRef,
		ResourceKey:            v.ResourceKey,
		EventFilters:           v.EventFilters,
		Status:                 v.Status,
		PausedReason:           v.PausedReason,
		CreatedByMemberID:      v.CreatedByMemberID,
		CreatedByActorType:     v.CreatedByActorType,
		CreatedByActorID:       v.CreatedByActorID,
		AuthorizerUserID:       v.AuthorizerUserID,
		DeliveryPolicy:         v.DeliveryPolicy,
		NotifyPrincipal:        v.NotifyPrincipal,
		CreatedVia:             v.CreatedVia,
		TriggerID:              v.TriggerID,
		Kind:                   v.Kind,
		OwnerKind:              v.OwnerKind,
		SourceType:             v.SourceType,
		CorrelationID:          v.CorrelationID,
		LastEventAt:            v.LastEventAt,
		LastFailureAt:          v.LastFailureAt,
		LastFailureCode:        v.LastFailureCode,
		LastFailureMessage:     v.LastFailureMessage,
		CreatedAt:              v.CreatedAt,
		UpdatedAt:              v.UpdatedAt,
		PausedAt:               v.PausedAt,
		RemovedAt:              v.RemovedAt,
	}
}

func ToServiceEventSubscriptionResponses(items []app.ServiceEventSubscriptionView) []dto.ServiceEventSubscriptionResponse {
	if items == nil {
		return nil
	}
	out := make([]dto.ServiceEventSubscriptionResponse, 0, len(items))
	for _, item := range items {
		out = append(out, ToServiceEventSubscriptionResponse(item))
	}
	return out
}

func ToServiceEventSubscriptionActionResultResponse(v app.ServiceEventSubscriptionActionResult) dto.ServiceEventSubscriptionActionResultResponse {
	return dto.ServiceEventSubscriptionActionResultResponse{
		Status:        v.Status,
		AlreadyExists: v.AlreadyExists,
		Subscription:  ToServiceEventSubscriptionResponse(v.Subscription),
		BindingID:     v.BindingID,
		Message:       v.Message,
	}
}

func ToProviderWatchActionResultResponse(v app.ProviderWatchActionResult) dto.ProviderWatchActionResultResponse {
	return dto.ProviderWatchActionResultResponse{
		Status:        v.Status,
		AlreadyExists: v.AlreadyExists,
		Watch:         ToServiceEventSubscriptionResponse(v.Watch),
		BindingID:     v.BindingID,
		Message:       v.Message,
	}
}

// ToExtensionProviderConnectionResponse projects the secret-free provider
// connection state plus the declared credential fields.
func ToExtensionProviderConnectionResponse(
	view app.ExtensionProviderConnectionView,
	fields []runner.CredentialField,
	credentials map[string]string,
) dto.ExtensionProviderConnectionResponse {
	projected := dto.ExtensionProviderConnectionResponse{
		Slug:          view.Slug,
		Active:        view.Active,
		CredentialSet: view.CredentialSet,
		UpdatedAt:     view.UpdatedAt,
		Fields:        make([]dto.ExtensionProviderCredentialFieldResponse, 0, len(fields)),
	}
	for _, field := range fields {
		projected.Fields = append(projected.Fields, dto.ExtensionProviderCredentialFieldResponse{
			Name:        field.Name,
			DisplayName: field.DisplayName,
			Description: field.Description,
			Required:    field.Required,
			Secret:      field.Secret,
			Configured:  credentials[field.Name] != "",
		})
		if !field.Secret && credentials[field.Name] != "" {
			if projected.Values == nil {
				projected.Values = make(map[string]string)
			}
			projected.Values[field.Name] = credentials[field.Name]
		}
	}
	return projected
}

// ToPersonalInstallationResponse projects a service personal installation:
// identity, config, pinned runtime capability, active connections, and the
// per-caller manage flag. providerConnection, catalogVisibility, and
// inChannel are intentionally not provided on this surface.
func ToPersonalInstallationResponse(v installation.PersonalInstallation) dto.AppInstallationResponse {
	return dto.AppInstallationResponse{
		ID:                v.ID,
		AppID:             v.AppID,
		AppSlug:           v.AppSlug,
		AppName:           v.AppName,
		AvatarURL:         v.AvatarURL,
		InstanceKey:       v.InstanceKey,
		OwnershipKind:     v.OwnershipKind,
		MemberID:          v.MemberID,
		Handle:            v.Handle,
		Config:            v.Config,
		RuntimeCapability: ToAppRuntimeCapabilityResponse(v.RuntimeCapability),
		Connections:       ToRuntimeConnectionResponses(v.Connections),
		CanManageApp:      v.CanManage,
		Enabled:           v.Enabled,
		CreatedAt:         v.CreatedAt,
		UpdatedAt:         v.UpdatedAt,
	}
}

// ToDirectPersonalInstallationResponse keeps the command-acknowledgement
// contract: the config field is always present, even when empty.
func ToDirectPersonalInstallationResponse(v installation.PersonalInstallation) dto.DirectAppInstallationResponse {
	config := v.Config
	if config == nil {
		config = map[string]any{}
	}
	return dto.DirectAppInstallationResponse{
		AppInstallationResponse: ToPersonalInstallationResponse(v),
		Config:                  config,
	}
}

// ToPersonalInstallationResponses projects a service personal installation list.

// ToShareRequestResponse projects a service share request with the reviewed
// policy snapshot approvers act on.
func ToShareRequestResponse(v installation.ShareRequest) dto.InstallationShareRequestResponse {
	return dto.InstallationShareRequestResponse{
		ID:             v.ID,
		AppID:          v.AppID,
		AppName:        v.AppName,
		InstallationID: v.InstallationID,
		RequestKind:    "share",
		Status:         v.Status,
		Reason:         v.Reason,
		ReviewedPolicy: toReviewedPolicy(v.ReviewedPolicy),
		CreatedAt:      v.CreatedAt,
		UpdatedAt:      v.UpdatedAt,
		DecidedAt:      v.DecidedAt,
	}
}

func toReviewedPolicy(snapshot *installation.SharePolicySnapshot) *dto.InstallationSharePolicyResponse {
	if snapshot == nil {
		return nil
	}
	installationConfig := snapshot.InstallationConfig
	if installationConfig == nil {
		installationConfig = map[string]any{}
	}
	connections := make([]map[string]any, 0, len(snapshot.Connections))
	for _, connection := range snapshot.Connections {
		connections = append(connections, map[string]any{
			"id":                 connection.ID,
			"externalSubjectRef": connection.ExternalSubjectRef,
			"displayLabel":       connection.DisplayLabel,
			"status":             connection.Status,
		})
	}
	extensions := make([]map[string]any, 0, len(snapshot.Extensions))
	for _, extension := range snapshot.Extensions {
		extensions = append(extensions, map[string]any{
			"name":         extension.Name,
			"domain":       extension.Domain,
			"version":      extension.Version,
			"digest":       extension.Digest,
			"status":       extension.Status,
			"definitionId": extension.DefinitionID,
		})
	}
	scopes := snapshot.Scopes
	if scopes == nil {
		scopes = []string{}
	}
	return &dto.InstallationSharePolicyResponse{
		DefinitionID:       snapshot.DefinitionID,
		Version:            snapshot.Version,
		Scopes:             scopes,
		PolicyDigest:       snapshot.PolicyDigest,
		InstallationConfig: installationConfig,
		Connections:        connections,
		Extensions:         extensions,
	}
}

// ToShareRequestResponses projects a service share request list.
