package repotokens

import (
	"context"
	"fmt"
	"strings"

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

// Store persists repository-scoped credentials, their audit facts, and agent
// suppression records. Policy, generic authorization, and API projection stay
// in their owning service and API packages.
type Store struct{ db *gorm.DB }

// New constructs a repository access token persistence store.
func New(db *gorm.DB) *Store { return &Store{db: db} }

func (s *Store) ensureDB() error {
	if s == nil || s.db == nil {
		return fmt.Errorf("repository access token store is not configured")
	}
	return nil
}

// DB returns the configured transaction context for service-owned bounded
// queries that have not yet been promoted to a named store method.
func (s *Store) DB(ctx context.Context) (*gorm.DB, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	return s.db.WithContext(ctx), nil
}

// CreateToken atomically persists one credential and its initial audit facts.
func (s *Store) CreateToken(ctx context.Context, token Token, events ...Event) (Token, error) {
	db, err := s.DB(ctx)
	if err != nil {
		return Token{}, err
	}
	err = db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&token).Error; err != nil {
			return err
		}
		for i := range events {
			events[i].TokenID = token.ID
			events[i].PublicTokenID = token.PublicID
			events[i].AGSTokenID = token.AGSTokenID
			if strings.TrimSpace(events[i].ActorPrincipalID) == "" {
				events[i].ActorPrincipalID = "system"
			}
			if err := tx.Create(&events[i]).Error; err != nil {
				return err
			}
		}
		return nil
	})
	return token, err
}

// GetByPublicID returns one workspace-scoped credential by its public ID.
func (s *Store) GetByPublicID(ctx context.Context, workspaceID, publicID string) (Token, error) {
	db, err := s.DB(ctx)
	if err != nil {
		return Token{}, err
	}
	var token Token
	err = db.Where("workspace_id = ? AND public_id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(publicID)).First(&token).Error
	return token, err
}

// SuppressAgent persists or updates the suppression decision for one agent.
func (s *Store) SuppressAgent(ctx context.Context, suppression AgentSuppression) error {
	db, err := s.DB(ctx)
	if err != nil {
		return err
	}
	return db.Clauses(clause.OnConflict{
		Columns: []clause.Column{{Name: "workspace_id"}, {Name: "agent_id"}},
		DoUpdates: clause.Assignments(map[string]any{
			"actor_principal_id": suppression.ActorPrincipalID,
			"reason":             suppression.Reason,
			"suppressed_at":      suppression.SuppressedAt,
			"updated_at":         suppression.UpdatedAt,
		}),
	}).Create(&suppression).Error
}

// AgentSuppressed reports whether one workspace agent has a persisted
// credential-recreation suppression record.
func (s *Store) AgentSuppressed(ctx context.Context, workspaceID, agentID string) (bool, error) {
	db, err := s.DB(ctx)
	if err != nil {
		return false, err
	}
	var count int64
	err = db.Model(&AgentSuppression{}).
		Where("workspace_id = ? AND agent_id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(agentID)).
		Count(&count).Error
	return count > 0, err
}
