package memory

import (
	"context"
	"sync"
	"time"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

// APIKeyRepository is an in-memory implementation of port.APIKeyRepository.
type APIKeyRepository struct {
	keys     map[domain.APIKeyID]*domain.APIKey
	keysByHash map[string]domain.APIKeyID
	nextID   int
	mu       sync.RWMutex
}

// NewAPIKeyRepository creates a new in-memory API key repository.
func NewAPIKeyRepository() *APIKeyRepository {
	return &APIKeyRepository{
		keys:       make(map[domain.APIKeyID]*domain.APIKey),
		keysByHash: make(map[string]domain.APIKeyID),
	}
}

// Ensure APIKeyRepository implements port.APIKeyRepository at compile time.
var _ port.APIKeyRepository = (*APIKeyRepository)(nil)

// Create stores a new API key.
func (r *APIKeyRepository) Create(ctx context.Context, key *domain.APIKey, keyHash string) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.nextID++
	key.ID = domain.APIKeyID(itoa(r.nextID))
	key.CreatedAt = time.Now()

	// Store the key
	r.keys[key.ID] = key
	r.keysByHash[keyHash] = key.ID

	return nil
}

// GetByHash retrieves an API key by its hash.
func (r *APIKeyRepository) GetByHash(ctx context.Context, keyHash string) (*domain.APIKey, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	id, ok := r.keysByHash[keyHash]
	if !ok {
		return nil, domain.ErrKeyNotFound
	}

	key, ok := r.keys[id]
	if !ok {
		return nil, domain.ErrKeyNotFound
	}

	return key, nil
}

// Get retrieves an API key by ID.
func (r *APIKeyRepository) Get(ctx context.Context, id domain.APIKeyID) (*domain.APIKey, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	key, ok := r.keys[id]
	if !ok {
		return nil, domain.ErrKeyNotFound
	}

	return key, nil
}

// List returns all API keys (without secrets).
func (r *APIKeyRepository) List(ctx context.Context) ([]*domain.APIKey, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	keys := make([]*domain.APIKey, 0, len(r.keys))
	for _, key := range r.keys {
		keys = append(keys, key)
	}

	return keys, nil
}

// Revoke marks an API key as revoked.
func (r *APIKeyRepository) Revoke(ctx context.Context, id domain.APIKeyID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	key, ok := r.keys[id]
	if !ok {
		return domain.ErrKeyNotFound
	}

	if key.RevokedAt != nil {
		return domain.ErrKeyNotFound
	}

	now := time.Now()
	key.RevokedAt = &now
	return nil
}

// UpdateLastUsed updates the last used timestamp for a key.
func (r *APIKeyRepository) UpdateLastUsed(ctx context.Context, id domain.APIKeyID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	key, ok := r.keys[id]
	if !ok {
		return domain.ErrKeyNotFound
	}

	now := time.Now()
	key.LastUsedAt = &now
	return nil
}

// itoa converts an integer to a string.
func itoa(i int) string {
	if i == 0 {
		return "0"
	}

	var buf [20]byte
	pos := len(buf)
	negative := i < 0
	if negative {
		i = -i
	}

	for i > 0 {
		pos--
		buf[pos] = byte('0' + i%10)
		i /= 10
	}

	if negative {
		pos--
		buf[pos] = '-'
	}

	return string(buf[pos:])
}
