package extension

import (
	"bytes"
	"context"
	"fmt"
	"time"

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

// RuntimeConnection is the minimal raw-identity App connection projection used
// by Extension invocation. Identity strings are intentionally not trimmed:
// TiDB/MySQL PAD SPACE comparisons may select a tampered trailing-space row,
// and the service must reject that byte mismatch.
type RuntimeConnection struct {
	ID                    string
	WorkspaceID           string
	ServiceDefinitionID   string
	ServiceInstallationID string
	ServiceKind           string
	ExternalSubjectRef    string
	Credentials           []byte
	Status                string
	// CurrentCredentialDigest and Config are the secret-free generation
	// facets of the locked current read, compared against the catalog
	// snapshot so a concurrent reconnect cannot mix generations.
	CurrentCredentialDigest string
	Config                  []byte
}

// ListActiveAppRuntimeConnections locks every active connection owned by one
// App Installation. Auth-profile-to-connection-slot mapping belongs to the
// Service Installation Extension selection config; v1 requires exactly one
// active App connection and fails closed on zero or ambiguity.
func (tx *TxStore) ListActiveAppRuntimeConnections(
	ctx context.Context,
	installationID string,
) ([]RuntimeConnection, error) {
	if tx == nil || tx.db == nil {
		return nil, fmt.Errorf("extension store transaction is not configured")
	}
	if installationID == "" {
		return nil, fmt.Errorf("installationId is required")
	}
	rows, err := tx.db.Query(ctx, `
SELECT id, workspace_id, service_definition_id, service_installation_id,
       service_kind, external_subject_ref, credentials, status,
       COALESCE(config, JSON_OBJECT())
FROM service_connections
WHERE service_installation_id = $1
  AND service_kind = $2
  AND status = 'active'
ORDER BY created_at ASC, id ASC
FOR UPDATE`, installationID, serviceactor.KindApp)
	if err != nil {
		return nil, fmt.Errorf("list active App runtime connections: %w", err)
	}
	defer rows.Close()
	var result []RuntimeConnection
	for rows.Next() {
		var connection RuntimeConnection
		if err := rows.Scan(
			&connection.ID,
			&connection.WorkspaceID,
			&connection.ServiceDefinitionID,
			&connection.ServiceInstallationID,
			&connection.ServiceKind,
			&connection.ExternalSubjectRef,
			&connection.Credentials,
			&connection.Status,
			&connection.Config,
		); err != nil {
			return nil, fmt.Errorf(
				"scan active App runtime connection: %w",
				err,
			)
		}
		connection.CurrentCredentialDigest = CredentialEnvelopeDigest(
			connection.Credentials,
		)
		result = append(result, connection)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf(
			"iterate active App runtime connections: %w",
			err,
		)
	}
	return result, nil
}

// UpdateAppRuntimeConnectionCredentials atomically replaces one active App
// connection's encrypted credential envelope only when the caller still holds
// the exact bytes it resolved.
func (tx *TxStore) UpdateAppRuntimeConnectionCredentials(
	ctx context.Context,
	connectionID string,
	expected []byte,
	replacement []byte,
	now time.Time,
) (bool, error) {
	if tx == nil || tx.db == nil {
		return false, fmt.Errorf(
			"extension store transaction is not configured",
		)
	}
	if connectionID == "" ||
		len(expected) == 0 ||
		len(replacement) == 0 {
		return false, fmt.Errorf(
			"connectionId and encrypted credential envelopes are required",
		)
	}
	now = now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	var durableID, durableKind, durableStatus string
	var durableCredentials []byte
	if err := tx.db.QueryRow(ctx, `
SELECT id, service_kind, status, credentials
FROM service_connections
WHERE id = $1
  AND service_kind = 'app'
  AND status = 'active'
FOR UPDATE`, connectionID).Scan(
		&durableID,
		&durableKind,
		&durableStatus,
		&durableCredentials,
	); err != nil {
		if coredb.IsNotFound(err) {
			return false, nil
		}
		return false, fmt.Errorf(
			"lock App runtime connection credentials: %w",
			err,
		)
	}
	if durableID != connectionID ||
		durableKind != serviceactor.KindApp ||
		durableStatus != serviceactor.StatusActive ||
		!bytes.Equal(durableCredentials, expected) {
		return false, nil
	}
	result, err := tx.db.Exec(ctx, `
UPDATE service_connections
SET credentials = $2,
    updated_at = $3
WHERE id = $1
  AND service_kind = 'app'
  AND status = 'active'`, connectionID, replacement, now)
	if err != nil {
		return false, fmt.Errorf(
			"update App runtime connection credentials: %w",
			err,
		)
	}
	return result.RowsAffected() == 1, nil
}
