package app

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/app/installation"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

const (
	AppInitialCredentialsStatusReadyToClaim  = "ready_to_claim"
	AppInitialCredentialsStatusClaimed       = "claimed"
	AppInitialCredentialsStatusNotApplicable = "not_applicable"
)

// AppInitialCredentialsStatusView is a secret-free read model for installed
// App setup. It never identifies the original requester.
type AppInitialCredentialsStatusView struct {
	Status string
}

// AppInitialCredentialsClaimResult contains the initial credential set.
// Plaintext exists only in this response.
type AppInitialCredentialsClaimResult struct {
	CallbackSigningSecret string
	RunnerToken           string
}

type appInitialCredentialRequestState struct {
	ID                          string
	WorkspaceID                 string
	ServiceDefinitionID         string
	Status                      string
	RequestKind                 string
	ServiceInstallationID       string
	InitialCredentialsClaimedAt *time.Time
}

type appInitialCredentialsContext struct {
	workspaceMember access.HumanWorkspaceMember
	summary         serviceactor.InstallationSummary
	request         appInitialCredentialRequestState
	requestFound    bool
	callbackEnabled bool
	runnerEnabled   bool
}

// GetAppInitialCredentialsStatusForUser reports whether an authorized external
// App manager can claim its initial runtime credential set.
func (store *Store) GetAppInitialCredentialsStatusForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
) (AppInitialCredentialsStatusView, error) {
	loaded, err := store.loadAppInitialCredentialsContext(ctx, userID, workspaceID, installationID)
	if err != nil {
		return AppInitialCredentialsStatusView{}, err
	}
	if !loaded.requestFound || (!loaded.callbackEnabled && !loaded.runnerEnabled) {
		return AppInitialCredentialsStatusView{Status: AppInitialCredentialsStatusNotApplicable}, nil
	}
	return appInitialCredentialsStatusView(loaded.request), nil
}

// requireAppInitialCredentialClaimable verifies the request's creator
// attribution is still eligible and the credentials were not claimed yet.
func requireAppInitialCredentialClaimable(ctx context.Context, tx coredb.Tx, request appInitialCredentialRequestState) error {
	eligible, err := workspaceAppInitialCredentialEligible(
		ctx,
		tx,
		request.WorkspaceID,
		request.ServiceDefinitionID,
	)
	if err != nil {
		return err
	}
	if !eligible {
		return conflict("App initial credential creator attribution changed.")
	}
	if request.InitialCredentialsClaimedAt != nil {
		return conflict("App initial credentials were already claimed.")
	}
	return nil
}

// ClaimAppInitialCredentialsForUser issues the initial credential set exactly
// once. A successful claim is the setup boundary; saving or clearing the
// one-time plaintext is only a client-side action.
func (store *Store) ClaimAppInitialCredentialsForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
) (AppInitialCredentialsClaimResult, error) {
	loaded, err := store.loadAppInitialCredentialsContext(ctx, userID, workspaceID, installationID)
	if err != nil {
		return AppInitialCredentialsClaimResult{}, err
	}
	if !loaded.requestFound || (!loaded.callbackEnabled && !loaded.runnerEnabled) {
		return AppInitialCredentialsClaimResult{}, ErrForbidden
	}

	var result AppInitialCredentialsClaimResult
	err = coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, loaded.summary.Definition.ID); err != nil {
			return err
		}
		request, err := loadAppInitialCredentialRequestByIDForUpdate(ctx, tx, workspaceID, loaded.request.ID)
		if err != nil {
			return err
		}
		if !appInitialCredentialRequestMatchesInstallation(request, loaded.summary, workspaceID, installationID) {
			return conflict("App initial credentials are no longer claimable.")
		}
		currentMember, err := store.revalidateInstallationManagementForUpdate(
			ctx,
			tx,
			userID,
			workspaceID,
			loaded.workspaceMember.Member.ID,
			loaded.summary.Installation.ID,
			loaded.summary.Definition.ID,
		)
		if err != nil {
			return err
		}
		if err := requireAppInitialCredentialClaimable(ctx, tx, request); err != nil {
			return err
		}
		currentInstallation, callbackEnabled, runnerEnabled, serviceMember, err := store.lockCurrentAppInitialCredentialInstallation(
			ctx,
			tx,
			workspaceID,
			installationID,
			loaded.summary,
		)
		if err != nil {
			return err
		}
		if !callbackEnabled && !runnerEnabled {
			return conflict("This App has no initial runtime credentials.")
		}

		now := time.Now().UTC()
		if callbackEnabled {
			callbackSecret, err := secrets.NewToken(32)
			if err != nil {
				return fmt.Errorf("generate initial App callback signing secret: %w", err)
			}
			if err := store.setAppCallbackSigningSecretInTx(ctx, tx, currentInstallation.ServiceDefinitionID, callbackSecret, now); err != nil {
				return err
			}
			result.CallbackSigningSecret = callbackSecret
		}

		if runnerEnabled {
			metadata, err := appRunnerCredentialMetadata(nil)
			if err != nil {
				return err
			}
			created, err := store.serviceActorStore().InTx(tx).CreateRuntimeCredentialToken(ctx, serviceactor.RuntimeCredentialTokenCreate{
				RuntimeCredentialCreate: serviceactor.RuntimeCredentialCreate{
					WorkspaceID:           strings.TrimSpace(workspaceID),
					ServiceDefinitionID:   currentInstallation.ServiceDefinitionID,
					ServiceInstallationID: currentInstallation.ID,
					ServiceMemberID:       serviceMember.ID,
					ServiceKind:           serviceactor.KindApp,
					CredentialKind:        serviceactor.RuntimeCredentialKindRunnerStreamToken,
					TransportKind:         serviceactor.RuntimeTransportKindRunnerStream,
					Name:                  defaultAppRunnerCredentialName,
					Status:                serviceactor.RuntimeCredentialStatusActive,
					CreatedByMemberID:     currentMember.Member.ID,
					Now:                   now,
					Metadata:              metadata,
				},
			})
			if err != nil {
				return fmt.Errorf("issue initial App runner credential: %w", err)
			}
			result.RunnerToken = created.Token
		}

		update, err := tx.Exec(ctx, `
UPDATE service_installation_requests
SET initial_credentials_claimed_at = $1,
    updated_at = $1
WHERE id = $2
  AND workspace_id = $3
  AND status = $4
  AND request_kind = $5
  AND service_installation_id = $6
  AND initial_credentials_claimed_at IS NULL
`, now, request.ID, strings.TrimSpace(workspaceID), AppInstallationRequestStatusApproved,
			AppInstallationRequestKindInstall, strings.TrimSpace(installationID))
		if err != nil {
			return fmt.Errorf("claim initial App credentials: %w", err)
		}
		if update.RowsAffected() != 1 {
			return conflict("App initial credentials are no longer claimable.")
		}
		return nil
	})
	if err != nil {
		return AppInitialCredentialsClaimResult{}, err
	}
	return result, nil
}

func (store *Store) loadAppInitialCredentialsContext(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
) (appInitialCredentialsContext, error) {
	installationID = strings.TrimSpace(installationID)
	if installationID == "" {
		return appInitialCredentialsContext{}, badRequest("installationId is required")
	}
	workspaceMember, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return appInitialCredentialsContext{}, err
	}
	summary, err := store.requireActiveAppRuntimeInstallation(ctx, workspaceID, installationID, nil)
	if err != nil {
		return appInitialCredentialsContext{}, err
	}
	if !serviceActorSummaryVisibleInAppManagement(summary) {
		return appInitialCredentialsContext{}, ErrNotFound
	}
	if err := store.authorizeInstallationManagementFromSummary(
		ctx,
		store.db,
		workspaceID,
		workspaceMember,
		summary,
	); err != nil {
		return appInitialCredentialsContext{}, err
	}
	callbackEnabled, runnerEnabled, err := initialAppCredentialTransports(summary.Installation)
	if err != nil {
		return appInitialCredentialsContext{}, err
	}
	request, found, err := loadAppInitialCredentialRequestByInstallation(ctx, store.db, workspaceID, installationID)
	if err != nil {
		return appInitialCredentialsContext{}, err
	}
	if found {
		eligible, err := workspaceAppInitialCredentialEligible(
			ctx,
			store.db,
			request.WorkspaceID,
			request.ServiceDefinitionID,
		)
		if err != nil {
			return appInitialCredentialsContext{}, err
		}
		if !eligible {
			request = appInitialCredentialRequestState{}
			found = false
		}
	}
	return appInitialCredentialsContext{
		workspaceMember: workspaceMember,
		summary:         summary,
		request:         request,
		requestFound:    found,
		callbackEnabled: callbackEnabled,
		runnerEnabled:   runnerEnabled,
	}, nil
}

// workspaceAppInitialCredentialEligible keeps the initial credential path on
// workspace-created third-party Apps. Product Apps and definitions without a
// workspace attribution do not enter this lifecycle.
func workspaceAppInitialCredentialEligible(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
	serviceDefinitionID string,
) (bool, error) {
	var exists int
	err := queryer.QueryRow(ctx, `
SELECT 1
FROM service_definitions app
JOIN extension_catalog_attributions attribution
  ON attribution.workspace_id = $1
 AND attribution.service_definition_id = app.id
WHERE app.id = $2
  AND app.service_kind = 'app'
  AND JSON_UNQUOTE(JSON_EXTRACT(COALESCE(app.config, JSON_OBJECT()), '$.appKind')) = 'third_party'
LIMIT 1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(serviceDefinitionID)).Scan(&exists)
	if coredb.IsNotFound(err) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("load App initial credential workspace attribution: %w", err)
	}
	return true, nil
}

func (store *Store) lockCurrentAppInitialCredentialInstallation(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	installationID string,
	expected serviceactor.InstallationSummary,
) (serviceactor.Installation, bool, bool, serviceactor.Member, error) {
	txServiceActor := store.serviceActorStore().InTx(tx)
	installation, err := txServiceActor.LoadInstallationForUpdate(ctx, strings.TrimSpace(installationID))
	if err != nil {
		return serviceactor.Installation{}, false, false, serviceactor.Member{}, err
	}
	if !currentAppRuntimeInstallationMatchesSummary(installation, expected, workspaceID) {
		return serviceactor.Installation{}, false, false, serviceactor.Member{}, conflict("App record changed before the initial credential was claimed. Refresh and try again.")
	}
	member, err := txServiceActor.LoadMemberByInstallation(ctx, installation.ID)
	if err != nil {
		return serviceactor.Installation{}, false, false, serviceactor.Member{}, err
	}
	if strings.TrimSpace(member.ID) != strings.TrimSpace(expected.Member.ID) ||
		strings.TrimSpace(member.Status) != serviceactor.StatusActive {
		return serviceactor.Installation{}, false, false, serviceactor.Member{}, conflict("App service member changed before initial credential setup.")
	}
	callbackEnabled, runnerEnabled, err := initialAppCredentialTransports(installation)
	if err != nil {
		return serviceactor.Installation{}, false, false, serviceactor.Member{}, err
	}
	return installation, callbackEnabled, runnerEnabled, member, nil
}

func initialAppCredentialTransports(installation serviceactor.Installation) (bool, bool, error) {
	capability, err := requirePinnedRuntimeCapability(installation)
	if err != nil {
		return false, false, err
	}
	return containsTrimmedString(capability.Transports, serviceactor.RuntimeTransportKindCallback),
		containsTrimmedString(capability.Transports, serviceactor.RuntimeTransportKindRunnerStream), nil
}

func appInitialCredentialsStatusView(request appInitialCredentialRequestState) AppInitialCredentialsStatusView {
	if request.InitialCredentialsClaimedAt != nil {
		return AppInitialCredentialsStatusView{Status: AppInitialCredentialsStatusClaimed}
	}
	return AppInitialCredentialsStatusView{Status: AppInitialCredentialsStatusReadyToClaim}
}

func loadAppInitialCredentialRequestByInstallation(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
	installationID string,
) (appInitialCredentialRequestState, bool, error) {
	row := queryer.QueryRow(ctx, appInitialCredentialRequestSelectSQL()+`
WHERE workspace_id = $1
  AND service_installation_id = $2
  AND service_kind = $3
  AND request_kind = $4
  AND status = $5
ORDER BY decided_at DESC, id DESC
LIMIT 1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(installationID), serviceactor.KindApp,
		AppInstallationRequestKindInstall, AppInstallationRequestStatusApproved)
	state, err := scanAppInitialCredentialRequest(row)
	if coredb.IsNotFound(err) {
		return appInitialCredentialRequestState{}, false, nil
	}
	if err != nil {
		return appInitialCredentialRequestState{}, false, fmt.Errorf("load initial App credential request: %w", err)
	}
	return state, true, nil
}

func loadAppInitialCredentialRequestByIDForUpdate(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
	requestID string,
) (appInitialCredentialRequestState, error) {
	row := queryer.QueryRow(ctx, appInitialCredentialRequestSelectSQL()+`
WHERE workspace_id = $1
  AND id = $2
FOR UPDATE
`, strings.TrimSpace(workspaceID), strings.TrimSpace(requestID))
	state, err := scanAppInitialCredentialRequest(row)
	if coredb.IsNotFound(err) {
		return appInitialCredentialRequestState{}, ErrNotFound
	}
	if err != nil {
		return appInitialCredentialRequestState{}, fmt.Errorf("lock initial App credential request: %w", err)
	}
	return state, nil
}

func appInitialCredentialRequestSelectSQL() string {
	return `
SELECT id,
       workspace_id,
       service_definition_id,
       status,
       request_kind,
       COALESCE(service_installation_id, ''),
       initial_credentials_claimed_at
FROM service_installation_requests
`
}

func scanAppInitialCredentialRequest(row coredb.Row) (appInitialCredentialRequestState, error) {
	var state appInitialCredentialRequestState
	var claimedAt sql.NullTime
	if err := row.Scan(
		&state.ID,
		&state.WorkspaceID,
		&state.ServiceDefinitionID,
		&state.Status,
		&state.RequestKind,
		&state.ServiceInstallationID,
		&claimedAt,
	); err != nil {
		return appInitialCredentialRequestState{}, err
	}
	state.ID = strings.TrimSpace(state.ID)
	state.WorkspaceID = strings.TrimSpace(state.WorkspaceID)
	state.ServiceDefinitionID = strings.TrimSpace(state.ServiceDefinitionID)
	state.Status = strings.TrimSpace(state.Status)
	state.RequestKind = strings.TrimSpace(state.RequestKind)
	state.ServiceInstallationID = strings.TrimSpace(state.ServiceInstallationID)
	if claimedAt.Valid {
		value := claimedAt.Time.UTC()
		state.InitialCredentialsClaimedAt = &value
	}
	return state, nil
}

func appInitialCredentialRequestMatchesInstallation(
	request appInitialCredentialRequestState,
	summary serviceactor.InstallationSummary,
	workspaceID string,
	installationID string,
) bool {
	return strings.TrimSpace(request.WorkspaceID) == strings.TrimSpace(workspaceID) &&
		strings.TrimSpace(request.ServiceDefinitionID) == strings.TrimSpace(summary.Definition.ID) &&
		strings.TrimSpace(request.ServiceInstallationID) == strings.TrimSpace(installationID) &&
		strings.TrimSpace(request.Status) == AppInstallationRequestStatusApproved &&
		strings.TrimSpace(request.RequestKind) == AppInstallationRequestKindInstall
}
