package installation

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

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

// LockAppInstallationMutationScope serializes mutations for one App
// definition. The definition row is the stable lock target because optional
// request and installation rows may not exist yet.
func LockAppInstallationMutationScope(ctx context.Context, queryer coredb.QueryRower, serviceDefinitionID string) error {
	locked, err := LockExistingAppInstallationMutationScope(ctx, queryer, serviceDefinitionID)
	if err != nil {
		return err
	}
	if !locked {
		return ErrNotFound
	}
	return nil
}

// LockExistingAppInstallationMutationScope locks an existing App definition.
// A missing definition is reported as an optional result for request paths that
// have not resolved a canonical definition yet.
func LockExistingAppInstallationMutationScope(ctx context.Context, queryer coredb.QueryRower, serviceDefinitionID string) (bool, error) {
	var lockedDefinitionID string
	if err := queryer.QueryRow(ctx, `
SELECT id
FROM service_definitions
WHERE id = $1
  AND service_kind = $2
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(serviceDefinitionID), serviceactor.KindApp).Scan(&lockedDefinitionID); err != nil {
		if coredb.IsNotFound(err) {
			return false, nil
		}
		return false, fmt.Errorf("lock App installation mutation scope: %w", err)
	}
	return true, nil
}

// LockCurrentWorkspaceAppInstallation locks one active attributed App
// installation after the caller has acquired the definition mutation fence.
func LockCurrentWorkspaceAppInstallation(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
	installationID string,
	appID string,
) error {
	var marker string
	if err := queryer.QueryRow(ctx, `
SELECT installation.id
FROM service_installations installation
JOIN extension_catalog_attributions attribution
  ON attribution.workspace_id = installation.workspace_id
 AND attribution.service_definition_id = installation.service_definition_id
WHERE installation.id = $1
  AND installation.workspace_id = $2
  AND installation.service_definition_id = $3
  AND installation.service_kind = $4
  AND installation.status = 'active'
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(installationID), strings.TrimSpace(workspaceID), strings.TrimSpace(appID), serviceactor.KindApp).Scan(&marker); err != nil {
		if coredb.IsNotFound(err) {
			return ErrNotFound
		}
		return fmt.Errorf("lock workspace App installation: %w", err)
	}
	return nil
}

// LockAppDefinitionScopeBatch takes the canonical App-definition mutation
// lock for every definition in one sorted batch.
func LockAppDefinitionScopeBatch(ctx context.Context, tx coredb.Tx, definitionIDs []string) error {
	if len(definitionIDs) == 0 {
		return nil
	}
	sorted := make([]string, len(definitionIDs))
	copy(sorted, definitionIDs)
	sort.Strings(sorted)
	placeholders := make([]string, 0, len(sorted))
	args := make([]any, 0, len(sorted))
	for _, definitionID := range sorted {
		args = append(args, definitionID)
		placeholders = append(placeholders, fmt.Sprintf("$%d", len(args)))
	}
	rows, err := tx.Query(ctx, fmt.Sprintf(`
SELECT id
FROM service_definitions
WHERE id IN (%s)
  AND service_kind = 'app'
ORDER BY id ASC
FOR UPDATE
`, strings.Join(placeholders, ", ")), args...)
	if err != nil {
		return fmt.Errorf("lock App definition mutation scopes: %w", err)
	}
	defer rows.Close()
	locked := 0
	for rows.Next() {
		locked++
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("iterate App definition mutation scopes: %w", err)
	}
	if locked != len(sorted) {
		return sql.ErrNoRows
	}
	return nil
}
