package app

import (
	"bytes"
	"context"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/app/installation"
	"github.com/tidbcloud/AgentTeam/internal/store/provider"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// SaveGiteaAppConfigForWorkspaceAdmin is the explicit privileged counterpart
// to member configuration requests. Ordinary-member proposals remain pending
// and are revalidated only if an administrator later approves them; obsolete
// proposals from requesters who have since become admins are withdrawn before
// the direct command. On first configuration it atomically
// installs the builtin Gitea App and stores the supplied credentials; no generic
// direct-install route is exposed. Blank secrets mean keep the existing values
// only after the App has already been configured.
func (store *Store) SaveGiteaAppConfigForWorkspaceAdmin(
	ctx context.Context,
	userID string,
	workspaceID string,
	params provider.UpsertGiteaAppConfigParams,
) (provider.GiteaAppConfigView, error) {
	if _, err := store.requireWorkspaceAdmin(ctx, userID, workspaceID); err != nil {
		return provider.GiteaAppConfigView{}, err
	}
	if err := store.settlePrivilegedGiteaPendingRequests(ctx, workspaceID); err != nil {
		return provider.GiteaAppConfigView{}, err
	}

	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, giteaAppID); err != nil {
			return err
		}
		adminMember, err := store.loadAuthorizedHumanWorkspaceMemberForUpdate(ctx, tx, userID, workspaceID, serviceInstallationApproveAction)
		if err != nil {
			return err
		}
		installation, found, err := store.serviceActorStore().InTx(tx).LoadInstallationByInstanceForUpdate(
			ctx,
			workspaceID,
			giteaAppID,
			serviceactor.DefaultInstanceKey,
		)
		if err != nil {
			return err
		}
		installed := found && strings.TrimSpace(installation.Status) == serviceactor.StatusActive && strings.TrimSpace(installation.ServiceKind) == serviceactor.KindApp
		now := time.Now().UTC()
		if !installed {
			prepared, err := prepareAppInstallationAtVersion(ctx, tx, giteaAppID, giteaAppSlug, "", nil)
			if err != nil {
				return err
			}
			definition, err := store.syncAppServiceDefinition(ctx, tx, prepared.app.ID, now)
			if err != nil {
				return err
			}
			if strings.TrimSpace(definition.ID) != giteaAppID {
				return conflict("Gitea App definition changed. Retry the configuration.")
			}
			if _, err := store.installPreparedAppInTx(ctx, tx, userID, workspaceID, adminMember.Member.ID, prepared, now); err != nil {
				return err
			}
		}

		base, baseFound, requested, err := store.providerStore().PrepareGiteaAppConfigRequestInTx(
			ctx,
			tx,
			workspaceID,
			params,
			installed,
		)
		if err != nil {
			return err
		}
		if err := store.requireNoActiveGiteaConnectionsForInstanceChangeInTx(
			ctx,
			tx,
			workspaceID,
			base,
			baseFound,
			requested,
		); err != nil {
			return err
		}
		return store.providerStore().ApplyApprovedGiteaAppConfigInTx(
			ctx,
			tx,
			workspaceID,
			userID,
			base,
			baseFound,
			requested,
			now,
		)
	})
	if err != nil {
		return provider.GiteaAppConfigView{}, err
	}

	// Configuration is the durable truth. Subscription convergence remains
	// best-effort, matching the approved configuration path.
	_, _ = store.UnblockServiceEventSubscriptions(ctx, workspaceID, giteaAppSlug, "")
	return store.providerStore().GetGiteaAppConfigForUser(ctx, userID, workspaceID)
}

// CreateGiteaConnectionForWorkspaceAdmin creates a connection immediately for
// a current workspace admin. Remote I/O happens outside database transactions;
// the final transaction rechecks admin authority, installation/config identity,
// and the exact connection base. Pending member proposals are not a write lock;
// if one later targets changed state, its own approval CAS reports it stale. A losing or
// failed attempt compensates only the webhook id it created.
func (store *Store) CreateGiteaConnectionForWorkspaceAdmin(
	ctx context.Context,
	userID string,
	workspaceID string,
	targetInput string,
) (provider.AppInstallationView, error) {
	if _, err := store.requireWorkspaceAdmin(ctx, userID, workspaceID); err != nil {
		return provider.AppInstallationView{}, err
	}
	if _, err := store.preflightDirectGiteaAdminMutation(ctx, userID, workspaceID); err != nil {
		return provider.AppInstallationView{}, err
	}
	target, err := parseGiteaConnectionTargetInput(targetInput)
	if err != nil {
		return provider.AppInstallationView{}, badRequest(err.Error())
	}
	config, found, err := store.providerStore().LoadGiteaAppConfigForWorkspace(ctx, workspaceID)
	if err != nil {
		return provider.AppInstallationView{}, err
	}
	if !found {
		return provider.AppInstallationView{}, badRequest("Gitea App is not configured.")
	}

	// Check the request/admin/config boundary before the first provider call.
	if _, _, err := store.captureDirectGiteaConnectBase(ctx, userID, workspaceID, config, target.ExternalSubjectRef); err != nil {
		return provider.AppInstallationView{}, err
	}
	verifiedTarget, err := store.verifyGiteaConnectionTarget(ctx, config, target)
	if err != nil {
		return provider.AppInstallationView{}, formatGiteaTargetVerificationError("Failed to verify Gitea target", err)
	}
	installationID, expectedConnection, err := store.captureDirectGiteaConnectBase(ctx, userID, workspaceID, config, verifiedTarget.ExternalSubjectRef)
	if err != nil {
		return provider.AppInstallationView{}, err
	}

	hooks, err := store.listGiteaConnectionWebhooks(ctx, config, verifiedTarget)
	if err != nil {
		return provider.AppInstallationView{}, formatGiteaConnectionError("Failed to inspect existing Gitea webhooks", err)
	}
	oldWebhookIDs := store.findAgentTeamGiteaConnectionWebhookIDs(hooks, workspaceID)
	webhookSecret := provider.DeriveGiteaConnectionWebhookSecret(config.WebhookSecret, verifiedTarget.ExternalSubjectRef)
	createdWebhookID, err := store.createGiteaConnectionWebhook(ctx, config, verifiedTarget, webhookSecret)
	if err != nil {
		return provider.AppInstallationView{}, formatGiteaConnectionError("Failed to create Gitea webhook", err)
	}

	applied := false
	applyErr := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, giteaAppID); err != nil {
			return err
		}
		if _, err := store.loadAuthorizedHumanWorkspaceMemberForUpdate(ctx, tx, userID, workspaceID, serviceInstallationApproveAction); err != nil {
			return err
		}
		installation, found, err := store.serviceActorStore().InTx(tx).LoadInstallationByInstanceForUpdate(
			ctx,
			workspaceID,
			giteaAppID,
			serviceactor.DefaultInstanceKey,
		)
		if err != nil {
			return err
		}
		if !found || strings.TrimSpace(installation.ID) != strings.TrimSpace(installationID) || strings.TrimSpace(installation.Status) != serviceactor.StatusActive || strings.TrimSpace(installation.ServiceKind) != serviceactor.KindApp {
			return conflict("Gitea App installation changed. Retry the connection.")
		}
		if err := validateCurrentGiteaConfigInTx(ctx, tx, workspaceID, config); err != nil {
			return err
		}
		current, currentFound, err := store.serviceActorStore().InTx(tx).LoadActiveConnectionForUpdate(ctx, installation.ID, verifiedTarget.ExternalSubjectRef)
		if err != nil {
			return err
		}
		if !directGiteaConnectionBaseEqual(expectedConnection, current, currentFound) {
			return conflict("Gitea connection changed. Retry the connection.")
		}
		if _, err := store.upsertAppConnectionInTx(ctx, tx, UpsertAppConnectionParams{
			InstallationID:     installation.ID,
			ExternalSubjectRef: verifiedTarget.ExternalSubjectRef,
			DisplayLabel:       verifiedTarget.DisplayLabel,
			Credentials: map[string]any{
				giteaConfigWebhookSecretKey:          webhookSecret,
				giteaConnectionRequestWebhookIDKey:   createdWebhookID,
				giteaConnectionRequestInstanceURLKey: strings.TrimRight(strings.TrimSpace(config.InstanceURL), "/"),
			},
			CreatedByUserID: userID,
		}, "", time.Now().UTC()); err != nil {
			return err
		}
		applied = true
		return nil
	})
	if applyErr != nil || !applied {
		cleanupCtx, cleanupCancel := context.WithTimeout(context.WithoutCancel(ctx), giteaConnectionAPIRequestTimeout)
		cleanupErr := store.deleteGiteaConnectionWebhookByID(cleanupCtx, config, verifiedTarget, createdWebhookID)
		cleanupCancel()
		if cleanupErr != nil && store.logger != nil {
			store.logger.Warn("compensate uncommitted direct gitea webhook", "target", verifiedTarget.DisplayLabel, "webhookID", createdWebhookID, "error", cleanupErr)
		}
		if applyErr != nil {
			return provider.AppInstallationView{}, applyErr
		}
		return provider.AppInstallationView{}, conflict("Gitea connection changed. Retry the connection.")
	}

	reconciliation := AppInstallationRequestView{
		InstallationID:   installationID,
		ConnectionTarget: verifiedTarget.ExternalSubjectRef,
		ConnectionLabel:  verifiedTarget.DisplayLabel,
		storedRequestedConfig: map[string]any{
			giteaConnectionRequestWebhookIDKey:   createdWebhookID,
			giteaConnectionRequestWebhookIDsKey:  append([]int64(nil), oldWebhookIDs...),
			giteaConnectionRequestInstanceURLKey: strings.TrimRight(strings.TrimSpace(config.InstanceURL), "/"),
			connectionRequestTargetKey:           verifiedTarget.ExternalSubjectRef,
			connectionRequestDisplayLabelKey:     verifiedTarget.DisplayLabel,
		},
	}
	reconcileCtx, reconcileCancel := context.WithTimeout(context.WithoutCancel(ctx), giteaConnectionAPIRequestTimeout)
	store.reconcileApprovedGiteaConnect(reconcileCtx, workspaceID, reconciliation)
	reconcileCancel()
	installation, err := store.appInstallationViewByID(ctx, workspaceID, installationID)
	if err != nil {
		return provider.AppInstallationView{}, err
	}
	return providerAppInstallationFromStore(installation), nil
}

// DisconnectGiteaConnectionForWorkspaceAdmin atomically disables the local
// connection/endpoints and blocks subscriptions. Exact owned webhook cleanup is
// then attempted best-effort against the same Gitea instance recorded on the
// connection; local settlement never depends on provider availability.
func (store *Store) DisconnectGiteaConnectionForWorkspaceAdmin(
	ctx context.Context,
	userID string,
	workspaceID string,
	connectionID string,
) error {
	if _, err := store.requireWorkspaceAdmin(ctx, userID, workspaceID); err != nil {
		return err
	}
	if err := store.settlePrivilegedGiteaPendingRequests(ctx, workspaceID); err != nil {
		return err
	}
	installationID, expectedConnection, baseSnapshot, err := store.captureDirectGiteaDisconnectBase(ctx, userID, workspaceID, connectionID)
	if err != nil {
		return err
	}

	cleanupSnapshot := cloneAppInstallationRequestConfig(baseSnapshot)

	err = coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, giteaAppID); err != nil {
			return err
		}
		if _, err := store.loadAuthorizedHumanWorkspaceMemberForUpdate(ctx, tx, userID, workspaceID, serviceInstallationApproveAction); err != nil {
			return err
		}
		installation, found, err := store.serviceActorStore().InTx(tx).LoadInstallationByInstanceForUpdate(
			ctx,
			workspaceID,
			giteaAppID,
			serviceactor.DefaultInstanceKey,
		)
		if err != nil {
			return err
		}
		if !found || strings.TrimSpace(installation.ID) != strings.TrimSpace(installationID) || strings.TrimSpace(installation.Status) != serviceactor.StatusActive || strings.TrimSpace(installation.ServiceKind) != serviceactor.KindApp {
			return conflict("Gitea App installation changed. Retry the disconnect.")
		}
		current, currentFound, err := loadActiveGiteaConnectionByIDForUpdate(ctx, tx, installation.ID, connectionID)
		if err != nil {
			return err
		}
		if !directGiteaConnectionBaseEqual(expectedConnection, current, currentFound) {
			return conflict("Gitea connection changed. Retry the disconnect.")
		}
		now := time.Now().UTC()
		if err := store.disableAppConnectionInTx(ctx, tx, installation.ID, connectionID, now); err != nil {
			return err
		}
		return store.blockGiteaServiceEventSubscriptionsInTx(ctx, tx, workspaceID, current.ExternalSubjectRef, now)
	})
	if err != nil {
		return err
	}

	reconciliation := AppInstallationRequestView{
		InstallationID:   installationID,
		ConnectionTarget: strings.TrimSpace(expectedConnection.connection.ExternalSubjectRef),
		ConnectionLabel:  strings.TrimSpace(expectedConnection.connection.DisplayLabel),
		storedBaseConfig: cleanupSnapshot,
	}
	reconcileCtx, reconcileCancel := context.WithTimeout(context.WithoutCancel(ctx), giteaConnectionAPIRequestTimeout)
	store.reconcileApprovedGiteaDisconnect(reconcileCtx, workspaceID, reconciliation)
	reconcileCancel()
	return nil
}

func (store *Store) settlePrivilegedGiteaPendingRequests(ctx context.Context, workspaceID string) error {
	return coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		_, err := settlePrivilegedPendingAppInstallationRequestsInTx(
			ctx,
			tx,
			workspaceID,
			giteaAppID,
			time.Now().UTC(),
		)
		return err
	})
}

func (store *Store) preflightDirectGiteaAdminMutation(ctx context.Context, userID string, workspaceID string) (string, error) {
	if err := store.settlePrivilegedGiteaPendingRequests(ctx, workspaceID); err != nil {
		return "", err
	}
	var installationID string
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, giteaAppID); err != nil {
			return err
		}
		if _, err := store.loadAuthorizedHumanWorkspaceMemberForUpdate(ctx, tx, userID, workspaceID, serviceInstallationApproveAction); err != nil {
			return err
		}
		installation, found, err := store.serviceActorStore().InTx(tx).LoadInstallationByInstanceForUpdate(
			ctx,
			workspaceID,
			giteaAppID,
			serviceactor.DefaultInstanceKey,
		)
		if err != nil {
			return err
		}
		if !found || strings.TrimSpace(installation.Status) != serviceactor.StatusActive || strings.TrimSpace(installation.ServiceKind) != serviceactor.KindApp {
			return conflict("Gitea App must be installed before it can be changed directly.")
		}
		installationID = installation.ID
		return nil
	})
	return installationID, err
}

type directGiteaConnectionBase struct {
	found      bool
	connection serviceactor.Connection
}

func (store *Store) captureDirectGiteaConnectBase(
	ctx context.Context,
	userID string,
	workspaceID string,
	config provider.GiteaAppConfig,
	externalSubjectRef string,
) (string, directGiteaConnectionBase, error) {
	var installationID string
	var base directGiteaConnectionBase
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, giteaAppID); err != nil {
			return err
		}
		if _, err := store.loadAuthorizedHumanWorkspaceMemberForUpdate(ctx, tx, userID, workspaceID, serviceInstallationApproveAction); err != nil {
			return err
		}
		installation, found, err := store.serviceActorStore().InTx(tx).LoadInstallationByInstanceForUpdate(
			ctx,
			workspaceID,
			giteaAppID,
			serviceactor.DefaultInstanceKey,
		)
		if err != nil {
			return err
		}
		if !found || strings.TrimSpace(installation.Status) != serviceactor.StatusActive || strings.TrimSpace(installation.ServiceKind) != serviceactor.KindApp {
			return conflict("Gitea App must be installed before creating a connection.")
		}
		if err := validateCurrentGiteaConfigInTx(ctx, tx, workspaceID, config); err != nil {
			return err
		}
		connection, connectionFound, err := store.serviceActorStore().InTx(tx).LoadActiveConnectionForUpdate(ctx, installation.ID, externalSubjectRef)
		if err != nil {
			return err
		}
		installationID = installation.ID
		base = directGiteaConnectionBase{found: connectionFound, connection: connection}
		return nil
	})
	return installationID, base, err
}

func (store *Store) captureDirectGiteaDisconnectBase(
	ctx context.Context,
	userID string,
	workspaceID string,
	connectionID string,
) (string, directGiteaConnectionBase, map[string]any, error) {
	var installationID string
	var base directGiteaConnectionBase
	var snapshot map[string]any
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, giteaAppID); err != nil {
			return err
		}
		if _, err := store.loadAuthorizedHumanWorkspaceMemberForUpdate(ctx, tx, userID, workspaceID, serviceInstallationApproveAction); err != nil {
			return err
		}
		installation, found, err := store.serviceActorStore().InTx(tx).LoadInstallationByInstanceForUpdate(
			ctx,
			workspaceID,
			giteaAppID,
			serviceactor.DefaultInstanceKey,
		)
		if err != nil {
			return err
		}
		if !found || strings.TrimSpace(installation.Status) != serviceactor.StatusActive || strings.TrimSpace(installation.ServiceKind) != serviceactor.KindApp {
			return ErrNotFound
		}
		connection, connectionFound, err := loadActiveGiteaConnectionByIDForUpdate(ctx, tx, installation.ID, connectionID)
		if err != nil {
			return err
		}
		if !connectionFound {
			return ErrNotFound
		}
		snapshot, err = store.giteaDisconnectConnectionSnapshot(connection)
		if err != nil {
			return err
		}
		installationID = installation.ID
		base = directGiteaConnectionBase{found: true, connection: connection}
		return nil
	})
	return installationID, base, snapshot, err
}

func directGiteaConnectionBaseEqual(expected directGiteaConnectionBase, current serviceactor.Connection, currentFound bool) bool {
	if expected.found != currentFound {
		return false
	}
	if !expected.found {
		return true
	}
	left := expected.connection
	return strings.TrimSpace(left.ID) == strings.TrimSpace(current.ID) &&
		strings.TrimSpace(left.WorkspaceID) == strings.TrimSpace(current.WorkspaceID) &&
		strings.TrimSpace(left.ServiceDefinitionID) == strings.TrimSpace(current.ServiceDefinitionID) &&
		strings.TrimSpace(left.ServiceInstallationID) == strings.TrimSpace(current.ServiceInstallationID) &&
		strings.TrimSpace(left.ExternalSubjectRef) == strings.TrimSpace(current.ExternalSubjectRef) &&
		strings.TrimSpace(left.DisplayLabel) == strings.TrimSpace(current.DisplayLabel) &&
		strings.TrimSpace(left.Status) == strings.TrimSpace(current.Status) &&
		left.UpdatedAt.Equal(current.UpdatedAt) &&
		bytes.Equal(left.Credentials, current.Credentials) &&
		bytes.Equal(left.Config, current.Config)
}
