package app

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

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

// FinalizeGitHubAppSetupForWorkspaceAdmin atomically activates the built-in AgentTeam
// integration and stores credentials only after GitHub has returned a verified
// registration or an existing GitHub App has been verified successfully.
// Opening the GitHub registration page therefore writes no durable state, and
// a local persistence failure cannot leave an installed but unconfigured App.
func (store *Store) FinalizeGitHubAppSetupForWorkspaceAdmin(
	ctx context.Context,
	userID string,
	workspaceID string,
	params provider.UpsertGitHubAppConfigParams,
) (provider.GitHubAppConfigView, error) {
	if _, err := store.requireWorkspaceAdmin(ctx, userID, workspaceID); err != nil {
		return provider.GitHubAppConfigView{}, err
	}
	if err := store.settlePrivilegedGitHubPendingRequests(ctx, workspaceID); err != nil {
		return provider.GitHubAppConfigView{}, err
	}

	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, registry.GitHubAppID); err != nil {
			return err
		}
		if err := deleteExpiredGitHubSetupVerificationsInTx(
			ctx, tx, workspaceID, time.Now().UTC().Add(-githubSetupVerificationTTL),
		); err != nil {
			return err
		}
		if err := requireNoGitHubSetupVerificationInTx(ctx, tx, workspaceID); err != nil {
			return err
		}
		adminMember, err := store.loadAuthorizedHumanWorkspaceMemberForUpdate(ctx, tx, userID, workspaceID, serviceInstallationApproveAction)
		if err != nil {
			return err
		}
		if err := releasePendingGitHubSetupClaimsForRegistrationInTx(ctx, tx, workspaceID); err != nil {
			return err
		}
		// A registration callback is an explicit administrator choice of a new
		// App. Remove only unsubmitted ready drafts before the generic config save
		// acquires the workspace-unique App claim; pending requests and durable
		// verification/patching leases are not setup-ready and remain untouched.
		if err := deleteAllGitHubSetupProposalsInTx(ctx, tx, workspaceID); err != nil {
			return err
		}
		_, err = store.finalizeGitHubAppSetupForWorkspaceAdminInTx(
			ctx, tx, userID, workspaceID, adminMember.Member.ID, params, "", "", time.Now().UTC(),
		)
		return err
	})
	if err != nil {
		return provider.GitHubAppConfigView{}, err
	}
	return githubConfigViewFromParams(params), nil
}

// releasePendingGitHubSetupClaimsForRegistrationInTx preserves ordinary
// members' pending audit rows while allowing an explicit administrator
// registration callback to claim the newly created App. The old request will
// deterministically fail its config/claim revalidation if later approved.
func releasePendingGitHubSetupClaimsForRegistrationInTx(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
) error {
	if _, err := tx.Exec(ctx, `
DELETE claim
FROM service_provider_workspace_claims claim
JOIN service_installation_requests req
  ON req.id = claim.claim_token
 AND req.workspace_id = claim.workspace_id
WHERE claim.workspace_id = $1
  AND claim.claim_state = $2
  AND req.service_definition_id = $3
  AND req.service_kind = $4
  AND req.request_kind = $5
  AND req.status = $6
`, strings.TrimSpace(workspaceID), githubAppClaimStateSetup, registry.GitHubAppID,
		serviceactor.KindApp, AppInstallationRequestKindInstall,
		AppInstallationRequestStatusPending); err != nil {
		return fmt.Errorf("release pending GitHub setup claims for administrator registration: %w", err)
	}
	return nil
}

func (store *Store) finalizeGitHubAppSetupForWorkspaceAdminInTx(
	ctx context.Context,
	tx coredb.Tx,
	userID string,
	workspaceID string,
	adminMemberID string,
	params provider.UpsertGitHubAppConfigParams,
	requestedVersion string,
	claimToken string,
	now time.Time,
) (string, error) {
	installation, found, err := store.serviceActorStore().InTx(tx).LoadInstallationByInstanceForUpdate(
		ctx,
		workspaceID,
		registry.GitHubAppID,
		serviceactor.DefaultInstanceKey,
	)
	if err != nil {
		return "", err
	}
	installed := found && strings.TrimSpace(installation.Status) == serviceactor.StatusActive && strings.TrimSpace(installation.ServiceKind) == serviceactor.KindApp
	installationID := strings.TrimSpace(installation.ID)
	if !installed {
		prepared, err := prepareAppInstallationAtVersion(ctx, tx, registry.GitHubAppID, registry.GitHubAppSlug, strings.TrimSpace(requestedVersion), 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) != registry.GitHubAppID {
			return "", conflict("GitHub App definition changed. Retry the setup.")
		}
		installationID, err = store.installPreparedAppInTx(ctx, tx, userID, workspaceID, adminMemberID, prepared, now)
		if err != nil {
			return "", err
		}
	}
	if strings.TrimSpace(claimToken) == "" {
		if err := store.providerStore().SaveGitHubAppConfigInTx(ctx, tx, userID, workspaceID, params, now); err != nil {
			return "", err
		}
	} else if err := store.providerStore().SaveGitHubAppConfigAfterSetupInTx(
		ctx, tx, userID, workspaceID, params, claimToken, now,
	); err != nil {
		return "", err
	}
	if err := deleteAllGitHubSetupProposalsInTx(ctx, tx, workspaceID); err != nil {
		return "", err
	}
	return installationID, nil
}

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