package memory

import (
	"context"
	"log/slog"
	"sync"
	"time"

	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/domain"
	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/port"
)

// Compile-time interface check.
var _ port.AuthCodeRepository = (*AuthCodeRepository)(nil)

// AuthCodeRepository is an in-memory auth code store for standalone development.
type AuthCodeRepository struct {
	mu    sync.RWMutex
	codes map[string]*domain.AuthCode
}

// NewAuthCodeRepository creates a new in-memory auth code repository.
func NewAuthCodeRepository() *AuthCodeRepository {
	return &AuthCodeRepository{
		codes: make(map[string]*domain.AuthCode),
	}
}

func (r *AuthCodeRepository) Create(_ context.Context, code *domain.AuthCode) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	cp := *code
	r.codes[code.ID] = &cp

	// In standalone dev mode the code lives only in memory and is lost on restart.
	// Always log it so the developer can copy-paste the code from the terminal
	// even when NOTIFY_URL is set and an email is also being delivered.
	slog.Warn("[DEV] auth code created — use this code to log in",
		"email", code.Email,
		"purpose", code.Purpose,
		"code", code.Code,
		"expires_at", code.ExpiresAt.Format("15:04:05"),
	)
	return nil
}

func (r *AuthCodeRepository) FindValid(_ context.Context, email string, code string, purpose domain.AuthCodePurpose) (*domain.AuthCode, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	for _, c := range r.codes {
		if c.Email == email && c.Code == code && c.Purpose == purpose && c.IsValid() {
			cp := *c
			return &cp, nil
		}
	}
	return nil, domain.ErrInvalidAuthCode
}

func (r *AuthCodeRepository) MarkUsed(_ context.Context, id string) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	c, ok := r.codes[id]
	if !ok {
		return domain.ErrInvalidAuthCode
	}
	now := time.Now()
	c.UsedAt = &now
	return nil
}

func (r *AuthCodeRepository) DeleteExpired(_ context.Context) (int, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now()
	deleted := 0
	for id, c := range r.codes {
		if now.After(c.ExpiresAt) {
			delete(r.codes, id)
			deleted++
		}
	}
	return deleted, nil
}
