package mailstore

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"
	"time"

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

// Connection is one exact active Email Connection. Credentials remain in the
// generic App encrypted envelope until the Mail runtime decrypts them in
// process. Generation changes whenever the stored credential or endpoint
// material changes, so an opaque mailbox token cannot survive reconnect.
type Connection struct {
	ID                    string
	WorkspaceID           string
	ServiceInstallationID string
	ExternalSubjectRef    string
	EncryptedCredentials  []byte
	Config                []byte
	Generation            string
	UpdatedAt             time.Time
}

// LoadActiveConnection resolves the exact active Connection selected by the
// trusted Extension invocation envelope.
func (store *Store) LoadActiveConnection(
	ctx context.Context,
	workspaceID string,
	installationID string,
	connectionID string,
) (Connection, error) {
	if err := store.Validate(); err != nil {
		return Connection{}, err
	}
	return loadActiveConnection(
		ctx,
		store.db,
		workspaceID,
		installationID,
		connectionID,
	)
}

func loadActiveConnection(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
	installationID string,
	connectionID string,
) (Connection, error) {
	workspaceID = strings.TrimSpace(workspaceID)
	installationID = strings.TrimSpace(installationID)
	connectionID = strings.TrimSpace(connectionID)
	if workspaceID == "" || installationID == "" || connectionID == "" {
		return Connection{}, storeerr.BadRequest(
			"Email Connection identity is incomplete",
		)
	}
	var connection Connection
	query := `
SELECT id, workspace_id, service_installation_id, external_subject_ref,
       credentials, config, updated_at
FROM service_connections
WHERE id = $1
  AND workspace_id = $2
  AND service_definition_id = $3
  AND service_installation_id = $4
  AND service_kind = $5
  AND status = 'active'
LIMIT 1`
	err := queryer.QueryRow(
		ctx,
		query,
		connectionID,
		workspaceID,
		registry.EmailAppID,
		installationID,
		serviceactor.KindApp).Scan(
		&connection.ID,
		&connection.WorkspaceID,
		&connection.ServiceInstallationID,
		&connection.ExternalSubjectRef,
		&connection.EncryptedCredentials,
		&connection.Config,
		&connection.UpdatedAt,
	)
	if coredb.IsNotFound(err) {
		return Connection{}, storeerr.NotFound(
			"Email Connection is unavailable",
		)
	}
	if err != nil {
		return Connection{}, fmt.Errorf("load exact Email Connection: %w", err)
	}
	connection.ID = strings.TrimSpace(connection.ID)
	connection.WorkspaceID = strings.TrimSpace(connection.WorkspaceID)
	connection.ServiceInstallationID = strings.TrimSpace(
		connection.ServiceInstallationID,
	)
	connection.ExternalSubjectRef = strings.TrimSpace(
		connection.ExternalSubjectRef,
	)
	if connection.ID != connectionID ||
		connection.WorkspaceID != workspaceID ||
		connection.ServiceInstallationID != installationID ||
		connection.ExternalSubjectRef == "" ||
		len(connection.EncryptedCredentials) == 0 ||
		len(connection.Config) == 0 {
		return Connection{}, storeerr.Conflict(
			"Email Connection material is invalid",
		)
	}
	connection.Generation = connectionGeneration(connection)
	return connection, nil
}

func connectionGeneration(connection Connection) string {
	digest := sha256.New()
	_, _ = digest.Write([]byte("agentteam/email/connection-generation/v1\x00"))
	writeGenerationPart(digest, []byte(connection.ID))
	writeGenerationPart(digest, connection.EncryptedCredentials)
	writeGenerationPart(digest, connection.Config)
	return "sha256:" + hex.EncodeToString(digest.Sum(nil))
}

type generationWriter interface {
	Write([]byte) (int, error)
}

func writeGenerationPart(writer generationWriter, value []byte) {
	length := []byte{
		byte(len(value) >> 24),
		byte(len(value) >> 16),
		byte(len(value) >> 8),
		byte(len(value)),
	}
	_, _ = writer.Write(length)
	_, _ = writer.Write(value)
}
