package automation

import (
	"context"
	"fmt"
	"strings"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// ListAutomationSecretsResult contains one page of automation secret metadata.
type ListAutomationSecretsResult struct {
	Secrets []AutomationSecret
	HasMore bool
}

// UpsertAutomationSecret stores one encrypted automation secret and returns its metadata row.
func (s *Store) UpsertAutomationSecret(ctx context.Context, secret AutomationSecret) (AutomationSecret, error) {
	if err := s.ensureDB(); err != nil {
		return AutomationSecret{}, err
	}
	secret = normalizeAutomationSecret(secret)
	if err := validateAutomationSecretKey(secret); err != nil {
		return AutomationSecret{}, err
	}
	if secret.ValueCiphertext == "" {
		return AutomationSecret{}, fmt.Errorf("automation secret ciphertext is required")
	}
	if err := s.db.WithContext(ctx).Clauses(clause.OnConflict{
		Columns: []clause.Column{
			{Name: "workspace_id"},
			{Name: "repository_owner"},
			{Name: "repository_name"},
			{Name: "name"},
		},
		DoUpdates: clause.AssignmentColumns([]string{
			"value_ciphertext",
			"updated_by",
			"updated_at",
		}),
	}).Create(&secret).Error; err != nil {
		return AutomationSecret{}, fmt.Errorf("upsert automation secret: %w", err)
	}
	return s.GetAutomationSecret(ctx, secret.WorkspaceID, secret.RepositoryOwner, secret.RepositoryName, secret.Name)
}

// ListAutomationSecrets lists automation secret metadata rows for one repository.
func (s *Store) ListAutomationSecrets(ctx context.Context, workspaceID string, owner string, repo string, page int, perPage int) (ListAutomationSecretsResult, error) {
	if err := s.ensureDB(); err != nil {
		return ListAutomationSecretsResult{}, err
	}
	key := normalizeAutomationSecretScope(workspaceID, owner, repo)
	if err := validateAutomationSecretScope(key); err != nil {
		return ListAutomationSecretsResult{}, err
	}
	offset := (page - 1) * perPage

	var secrets []AutomationSecret
	err := s.db.WithContext(ctx).
		Select("workspace_id", "repository_owner", "repository_name", "name", "created_by", "updated_by", "created_at", "updated_at").
		Where("workspace_id = ? AND repository_owner = ? AND repository_name = ?", key.WorkspaceID, key.RepositoryOwner, key.RepositoryName).
		Order("name ASC").
		Limit(perPage + 1).
		Offset(offset).
		Find(&secrets).Error
	if err != nil {
		return ListAutomationSecretsResult{}, fmt.Errorf("list automation secrets: %w", err)
	}
	hasMore := len(secrets) > perPage
	if hasMore {
		secrets = secrets[:perPage]
	}
	return ListAutomationSecretsResult{Secrets: secrets, HasMore: hasMore}, nil
}

// GetAutomationSecret loads one encrypted automation secret row.
func (s *Store) GetAutomationSecret(ctx context.Context, workspaceID string, owner string, repo string, name string) (AutomationSecret, error) {
	if err := s.ensureDB(); err != nil {
		return AutomationSecret{}, err
	}
	key := normalizeAutomationSecret(AutomationSecret{
		WorkspaceID:     workspaceID,
		RepositoryOwner: owner,
		RepositoryName:  repo,
		Name:            name,
	})
	if err := validateAutomationSecretKey(key); err != nil {
		return AutomationSecret{}, err
	}

	var secret AutomationSecret
	err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND repository_owner = ? AND repository_name = ? AND name = ?", key.WorkspaceID, key.RepositoryOwner, key.RepositoryName, key.Name).
		First(&secret).Error
	if err != nil {
		return AutomationSecret{}, err
	}
	return secret, nil
}

// GetAutomationSecretCiphertextsByNames loads encrypted automation secret values for explicit names.
func (s *Store) GetAutomationSecretCiphertextsByNames(ctx context.Context, workspaceID string, owner string, repo string, names []string) (map[string]string, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	key := normalizeAutomationSecretScope(workspaceID, owner, repo)
	if err := validateAutomationSecretScope(key); err != nil {
		return nil, err
	}
	normalizedNames := normalizeAutomationSecretNames(names)
	if len(normalizedNames) == 0 {
		return map[string]string{}, nil
	}

	var rows []struct {
		Name            string
		ValueCiphertext string
	}
	err := s.db.WithContext(ctx).
		Model(&AutomationSecret{}).
		Select("name", "value_ciphertext").
		Where("workspace_id = ? AND repository_owner = ? AND repository_name = ? AND name IN ?", key.WorkspaceID, key.RepositoryOwner, key.RepositoryName, normalizedNames).
		Find(&rows).Error
	if err != nil {
		return nil, fmt.Errorf("get automation secret ciphertexts by names: %w", err)
	}
	byName := make(map[string]string, len(rows))
	for _, row := range rows {
		byName[row.Name] = row.ValueCiphertext
	}
	return byName, nil
}

// GetExistingAutomationSecretNames returns existing secret names from an explicit candidate set.
func (s *Store) GetExistingAutomationSecretNames(ctx context.Context, workspaceID string, owner string, repo string, names []string) (map[string]struct{}, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	key := normalizeAutomationSecretScope(workspaceID, owner, repo)
	if err := validateAutomationSecretScope(key); err != nil {
		return nil, err
	}
	normalizedNames := normalizeAutomationSecretNames(names)
	if len(normalizedNames) == 0 {
		return map[string]struct{}{}, nil
	}

	var rows []struct {
		Name string
	}
	err := s.db.WithContext(ctx).
		Model(&AutomationSecret{}).
		Select("name").
		Where("workspace_id = ? AND repository_owner = ? AND repository_name = ? AND name IN ?", key.WorkspaceID, key.RepositoryOwner, key.RepositoryName, normalizedNames).
		Find(&rows).Error
	if err != nil {
		return nil, fmt.Errorf("get existing automation secret names: %w", err)
	}
	existing := make(map[string]struct{}, len(rows))
	for _, row := range rows {
		existing[row.Name] = struct{}{}
	}
	return existing, nil
}

// DeleteAutomationSecret deletes one encrypted automation secret row.
func (s *Store) DeleteAutomationSecret(ctx context.Context, workspaceID string, owner string, repo string, name string) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	key := normalizeAutomationSecret(AutomationSecret{
		WorkspaceID:     workspaceID,
		RepositoryOwner: owner,
		RepositoryName:  repo,
		Name:            name,
	})
	if err := validateAutomationSecretKey(key); err != nil {
		return err
	}
	result := s.db.WithContext(ctx).
		Where("workspace_id = ? AND repository_owner = ? AND repository_name = ? AND name = ?", key.WorkspaceID, key.RepositoryOwner, key.RepositoryName, key.Name).
		Delete(&AutomationSecret{})
	if result.Error != nil {
		return fmt.Errorf("delete automation secret: %w", result.Error)
	}
	if result.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func normalizeAutomationSecret(secret AutomationSecret) AutomationSecret {
	secret.WorkspaceID = strings.TrimSpace(secret.WorkspaceID)
	secret.RepositoryOwner = strings.TrimSpace(secret.RepositoryOwner)
	secret.RepositoryName = strings.TrimSpace(secret.RepositoryName)
	secret.Name = strings.TrimSpace(secret.Name)
	secret.ValueCiphertext = strings.TrimSpace(secret.ValueCiphertext)
	secret.CreatedBy = strings.TrimSpace(secret.CreatedBy)
	secret.UpdatedBy = strings.TrimSpace(secret.UpdatedBy)
	return secret
}

func validateAutomationSecretKey(secret AutomationSecret) error {
	if err := validateAutomationSecretScope(secret); err != nil {
		return err
	}
	if secret.Name == "" {
		return fmt.Errorf("automation secret name is required")
	}
	return nil
}

func normalizeAutomationSecretScope(workspaceID string, owner string, repo string) AutomationSecret {
	return AutomationSecret{
		WorkspaceID:     strings.TrimSpace(workspaceID),
		RepositoryOwner: strings.TrimSpace(owner),
		RepositoryName:  strings.TrimSpace(repo),
	}
}

func validateAutomationSecretScope(secret AutomationSecret) error {
	switch {
	case secret.WorkspaceID == "":
		return fmt.Errorf("automation secret workspace_id is required")
	case secret.RepositoryOwner == "":
		return fmt.Errorf("automation secret repository_owner is required")
	case secret.RepositoryName == "":
		return fmt.Errorf("automation secret repository_name is required")
	default:
		return nil
	}
}

func normalizeAutomationSecretNames(names []string) []string {
	seen := make(map[string]struct{}, len(names))
	normalized := make([]string, 0, len(names))
	for _, name := range names {
		name = strings.TrimSpace(name)
		if name == "" {
			continue
		}
		if _, ok := seen[name]; ok {
			continue
		}
		seen[name] = struct{}{}
		normalized = append(normalized, name)
	}
	return normalized
}
