package app

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

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// ServiceEventSubscriptionView is the canonical app-service event subscription view.
type ServiceEventSubscriptionView struct {
	ID                     string
	AppInstallationID      string
	AppBindingID           string
	ProviderConnectionRef  string
	Provider               string
	TargetKind             string
	TargetRef              string
	DeliveryScopeID        string
	PermissionCheckScopeID string
	ResourceKind           string
	ResourceRef            string
	ResourceKey            string
	EventFilters           map[string]any
	Status                 string
	PausedReason           string
	CreatedByMemberID      string
	CreatedByActorType     string
	CreatedByActorID       string
	AuthorizerUserID       string
	DeliveryPolicy         string
	NotifyPrincipal        bool
	CreatedVia             string
	TriggerID              string
	Kind                   string
	OwnerKind              string
	SourceType             string
	CorrelationID          string
	LastEventAt            *time.Time
	LastFailureAt          *time.Time
	LastFailureCode        string
	LastFailureMessage     string
	CreatedAt              time.Time
	UpdatedAt              time.Time
	PausedAt               *time.Time
	RemovedAt              *time.Time
}

// ServiceEventSubscriptionActionResult is the canonical action result for app
// service-event subscription lifecycle mutations.
type ServiceEventSubscriptionActionResult struct {
	Status        string
	AlreadyExists bool
	Subscription  ServiceEventSubscriptionView
	BindingID     string
	Message       string
}

// ListServiceEventSubscriptionsForUser lists service-event subscriptions for
// an installed app. The App-page API is scoped to the current human principal:
// a regular workspace member can see subscriptions created by themselves and
// by agents they own, but not subscriptions owned by other workspace members.
// Workspace owners/admins retain a workspace-wide management view.
func (store *Store) ListServiceEventSubscriptionsForUser(ctx context.Context, userID string, workspaceID string, installationID string, target string, resource string, status string, limit int) ([]ServiceEventSubscriptionView, error) {
	workspaceMember, err := store.loadAuthorizedHumanWorkspaceMember(ctx, store.db, userID, workspaceID, workspaceAccessAction)
	if err != nil {
		return nil, err
	}
	if err := store.requireInstallationUseForUser(ctx, workspaceMember, workspaceID, installationID); err != nil {
		return nil, err
	}
	installation, err := store.loadServiceEventSubscriptionInstallationForUser(ctx, workspaceID, installationID)
	if err != nil {
		return nil, err
	}
	var targetKind, targetRef string
	if strings.TrimSpace(target) != "" {
		resolved, err := ResolveServiceEventTargetForScope(ctx, store.db, workspaceID, workspaceMember.Member.ID, target)
		if err != nil {
			return nil, err
		}
		targetKind, targetRef = resolved.TargetKind, resolved.TargetRef
	}
	resourceKey := strings.TrimSpace(resource)
	if resourceKey != "" {
		normalized, err := NormalizePullRequestSubscriptionRef(installation.AppSlug, resourceKey)
		if err != nil {
			return nil, err
		}
		resourceKey = normalized.ResourceKey
	}
	actor := ServiceEventActor{ActorType: "human", ActorID: userID, MemberID: workspaceMember.Member.ID}
	subscriptions, err := store.ListServiceEventSubscriptions(ctx, workspaceID, actor, installation.ID, targetKind, targetRef, status, ListServiceEventSubscriptionsOptions{ResourceKey: resourceKey, Limit: limit})
	if err != nil {
		return nil, err
	}
	views := make([]ServiceEventSubscriptionView, 0, len(subscriptions))
	for _, subscription := range subscriptions {
		if resourceKey != "" && subscription.ResourceKey != resourceKey {
			continue
		}
		views = append(views, serviceEventSubscriptionRecordView(subscription))
	}
	return views, nil
}

// GetServiceEventSubscriptionForUser loads one service-event subscription
// visible to the current human principal.
func (store *Store) GetServiceEventSubscriptionForUser(ctx context.Context, userID string, workspaceID string, subscriptionID string) (ServiceEventSubscriptionView, error) {
	workspaceMember, err := store.loadAuthorizedHumanWorkspaceMember(ctx, store.db, userID, workspaceID, workspaceAccessAction)
	if err != nil {
		return ServiceEventSubscriptionView{}, err
	}
	subscription, err := loadServiceEventSubscriptionByID(ctx, store.db, workspaceID, subscriptionID)
	if err != nil {
		if coredb.IsNotFound(err) {
			return ServiceEventSubscriptionView{}, storeerr.ErrNotFound
		}
		return ServiceEventSubscriptionView{}, err
	}
	if subscription.RemovedAt != nil || !isPullRequestServiceEventSubscription(subscription) {
		return ServiceEventSubscriptionView{}, storeerr.ErrNotFound
	}
	actor := ServiceEventActor{ActorType: "human", ActorID: userID, MemberID: workspaceMember.Member.ID}
	if err := requireServiceEventActorOwnsSubscription(ctx, store.db, workspaceID, actor, subscription); err != nil {
		return ServiceEventSubscriptionView{}, err
	}
	requireTargetAccess, err := serviceEventSubscriptionHumanRequiresTargetAccess(ctx, store.db, workspaceID, userID, workspaceMember.Member.ID, workspaceMember.WorkspaceRole, subscription)
	if err != nil {
		return ServiceEventSubscriptionView{}, err
	}
	if requireTargetAccess {
		if err := authorizeServiceEventSubscriptionUserOperationTarget(ctx, store.db, workspaceID, workspaceMember.Member.ID, subscription); err != nil {
			return ServiceEventSubscriptionView{}, err
		}
	}
	return serviceEventSubscriptionRecordView(subscription), nil
}

// CreateServiceEventSubscriptionForUser creates or reuses an app service-event
// subscription from the App page.
func (store *Store) CreateServiceEventSubscriptionForUser(ctx context.Context, userID string, workspaceID string, installationID string, target string, prRefOrURL string, events []string, notifyPrincipal *bool) (ServiceEventSubscriptionActionResult, error) {
	workspaceMember, err := store.loadAuthorizedHumanWorkspaceMember(ctx, store.db, userID, workspaceID, workspaceAccessAction)
	if err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	if err := store.requireInstallationUseForUser(ctx, workspaceMember, workspaceID, installationID); err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	installation, err := store.loadServiceEventSubscriptionInstallationForUser(ctx, workspaceID, installationID)
	if err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	resolved, err := ResolveServiceEventTargetForScope(ctx, store.db, workspaceID, workspaceMember.Member.ID, target)
	if err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	resource, err := NormalizePullRequestSubscriptionRef(installation.AppSlug, prRefOrURL)
	if err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	result, err := store.CreateServiceEventSubscription(ctx, workspaceID, CreateServiceEventSubscriptionParams{
		Provider:        installation.AppSlug,
		InstallationID:  installation.ID,
		Actor:           ServiceEventActor{ActorType: "human", ActorID: userID, MemberID: workspaceMember.Member.ID},
		Target:          resolved,
		ResourceRef:     resource.ResourceRef,
		ResourceKey:     resource.ResourceKey,
		EventFilters:    serviceEventSubscriptionEventsMap(events),
		NotifyPrincipal: notifyPrincipal,
		CreatedVia:      "app_page",
		Metadata: map[string]any{
			"source": "app_page",
		},
	})
	if err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	createdSubscription := serviceEventSubscriptionResultRecord(result)
	view := serviceEventSubscriptionRecordView(createdSubscription)
	message := fmt.Sprintf("%s subscribed to %s in this target.", pullRequestSubscriptionProviderDisplayName(installation.AppSlug), resource.ResourceKey)
	if result.AlreadyExists {
		message = fmt.Sprintf("%s was already subscribed to %s in this target.", pullRequestSubscriptionProviderDisplayName(installation.AppSlug), resource.ResourceKey)
	}
	return ServiceEventSubscriptionActionResult{
		Status:        createdSubscription.Status,
		AlreadyExists: result.AlreadyExists,
		Subscription:  view,
		BindingID:     result.BindingID,
		Message:       message,
	}, nil
}

func (store *Store) PauseServiceEventSubscriptionForUser(ctx context.Context, userID string, workspaceID string, subscriptionID string) (ServiceEventSubscriptionActionResult, error) {
	return store.changeServiceEventSubscriptionForUser(ctx, userID, workspaceID, subscriptionID, "pause")
}

func (store *Store) ResumeServiceEventSubscriptionForUser(ctx context.Context, userID string, workspaceID string, subscriptionID string) (ServiceEventSubscriptionActionResult, error) {
	return store.changeServiceEventSubscriptionForUser(ctx, userID, workspaceID, subscriptionID, "resume")
}

func (store *Store) RemoveServiceEventSubscriptionForUser(ctx context.Context, userID string, workspaceID string, subscriptionID string) (ServiceEventSubscriptionActionResult, error) {
	return store.changeServiceEventSubscriptionForUser(ctx, userID, workspaceID, subscriptionID, "delete")
}

func authorizeServiceEventSubscriptionUserOperationTarget(ctx context.Context, queryer coredb.Queryer, workspaceID string, actorMemberID string, subscription ServiceEventSubscriptionRecord) error {
	trimmedActorMemberID := strings.TrimSpace(actorMemberID)
	var resolved ServiceEventTarget
	var err error
	switch strings.TrimSpace(subscription.TargetKind) {
	case "task":
		resolved, err = resolveServiceEventTaskTargetByID(ctx, queryer, workspaceID, trimmedActorMemberID, subscription.TargetRef)
	default:
		resolved, err = ResolveServiceEventTargetForScope(ctx, queryer, workspaceID, trimmedActorMemberID, subscription.DeliveryScopeID)
	}
	if err != nil {
		return err
	}
	if !serviceEventSubscriptionResolvedTargetMatchesRecord(resolved, subscription) {
		return storeerr.ErrNotFound
	}
	return nil
}

func serviceEventSubscriptionResolvedTargetMatchesRecord(resolved ServiceEventTarget, subscription ServiceEventSubscriptionRecord) bool {
	return strings.TrimSpace(resolved.TargetKind) == strings.TrimSpace(subscription.TargetKind) &&
		strings.TrimSpace(resolved.TargetRef) == strings.TrimSpace(subscription.TargetRef) &&
		strings.TrimSpace(resolved.DeliveryScopeID) == strings.TrimSpace(subscription.DeliveryScopeID) &&
		strings.TrimSpace(resolved.PermissionCheckScopeID) == strings.TrimSpace(subscription.PermissionCheckScopeID)
}

func (store *Store) changeServiceEventSubscriptionForUser(ctx context.Context, userID string, workspaceID string, subscriptionID string, operation string) (ServiceEventSubscriptionActionResult, error) {
	workspaceMember, err := store.loadAuthorizedHumanWorkspaceMember(ctx, store.db, userID, workspaceID, workspaceAccessAction)
	if err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	subscription, err := loadServiceEventSubscriptionByID(ctx, store.db, workspaceID, subscriptionID)
	if err != nil {
		if coredb.IsNotFound(err) {
			return ServiceEventSubscriptionActionResult{}, storeerr.ErrNotFound
		}
		return ServiceEventSubscriptionActionResult{}, err
	}
	if subscription.RemovedAt != nil || !isPullRequestServiceEventSubscription(subscription) {
		return ServiceEventSubscriptionActionResult{}, storeerr.ErrNotFound
	}
	actor := ServiceEventActor{ActorType: "human", ActorID: userID, MemberID: workspaceMember.Member.ID}
	if err := requireServiceEventActorOwnsSubscription(ctx, store.db, workspaceID, actor, subscription); err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	requireTargetAccess, err := serviceEventSubscriptionHumanRequiresTargetAccess(ctx, store.db, workspaceID, userID, workspaceMember.Member.ID, workspaceMember.WorkspaceRole, subscription)
	if err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	if requireTargetAccess {
		if err := authorizeServiceEventSubscriptionUserOperationTarget(ctx, store.db, workspaceID, workspaceMember.Member.ID, subscription); err != nil {
			return ServiceEventSubscriptionActionResult{}, err
		}
	}
	switch operation {
	case "pause":
		subscription, err = store.PauseServiceEventSubscription(ctx, workspaceID, actor, subscription.ID, "app_page")
	case "resume":
		subscription, err = store.ResumeServiceEventSubscription(ctx, workspaceID, actor, subscription.ID)
	case "delete":
		subscription, err = store.RemoveServiceEventSubscription(ctx, workspaceID, actor, subscription.ID)
	default:
		err = storeerr.BadRequest("service event subscription operation is not supported")
	}
	if err != nil {
		return ServiceEventSubscriptionActionResult{}, err
	}
	view := serviceEventSubscriptionRecordView(subscription)
	return ServiceEventSubscriptionActionResult{
		Status:       operation,
		Subscription: view,
		BindingID:    subscription.AppBindingID,
		Message:      fmt.Sprintf("Service event subscription %s.", operation),
	}, nil
}

func serviceEventSubscriptionWorkspaceManager(workspaceRole string) bool {
	return access.HasWorkspaceAdminRole(domain.WorkspaceRole(strings.TrimSpace(workspaceRole)))
}

// A human who owns the creator agent can manage that agent's subscription even
// when the target is visible only to the agent (for example an owner-private
// channel entry). Ownership is checked against the backing machine rather than
// the app authorizer. Human-created subscriptions retain the existing target
// access re-check, while workspace owners/admins use their explicit recovery
// authority.
func serviceEventSubscriptionHumanRequiresTargetAccess(ctx context.Context, queryer coredb.QueryRower, workspaceID string, userID string, memberID string, workspaceRole string, subscription ServiceEventSubscriptionRecord) (bool, error) {
	if serviceEventSubscriptionWorkspaceManager(workspaceRole) {
		return false, nil
	}
	ownedAgentSubscription, err := serviceEventSubscriptionCreatedByAgentOwnedByHuman(ctx, queryer, workspaceID, userID, memberID, subscription)
	if err != nil {
		return false, err
	}
	return !ownedAgentSubscription, nil
}

type serviceEventSubscriptionInstallationForUser struct {
	ID      string
	AppSlug string
}

func (store *Store) loadServiceEventSubscriptionInstallationForUser(ctx context.Context, workspaceID string, installationID string) (serviceEventSubscriptionInstallationForUser, error) {
	installation, err := store.installationFromServiceActor(ctx, workspaceID, installationID)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			return serviceEventSubscriptionInstallationForUser{}, storeerr.ErrNotFound
		}
		return serviceEventSubscriptionInstallationForUser{}, err
	}
	if !installation.Enabled {
		return serviceEventSubscriptionInstallationForUser{}, storeerr.ErrNotFound
	}
	if _, err := pullRequestSubscriptionProviderAppID(installation.AppSlug); err != nil {
		return serviceEventSubscriptionInstallationForUser{}, err
	}
	return serviceEventSubscriptionInstallationForUser{ID: installation.ID, AppSlug: installation.AppSlug}, nil
}

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

func serviceEventSubscriptionEventsMap(events []string) map[string]any {
	normalized := normalizeNonEmptyStrings(events)
	if len(normalized) == 0 {
		return map[string]any{}
	}
	return map[string]any{"events": normalized}
}

func decodeServiceEventSubscriptionJSONMap(payload []byte) map[string]any {
	if len(payload) == 0 {
		return map[string]any{}
	}
	var decoded map[string]any
	if err := model.UnmarshalJSONObject("service event subscription JSON map", payload, &decoded); err != nil || decoded == nil {
		return map[string]any{}
	}
	return decoded
}
