package memory

import (
	"context"
	"sync"
	"time"

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

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

// SessionRepository is an in-memory session store for standalone development.
type SessionRepository struct {
	mu       sync.RWMutex
	sessions map[domain.SessionID]*domain.Session
}

// NewSessionRepository creates a new in-memory session repository.
func NewSessionRepository() *SessionRepository {
	return &SessionRepository{
		sessions: make(map[domain.SessionID]*domain.Session),
	}
}

func (r *SessionRepository) copySession(s *domain.Session) *domain.Session {
	cp := *s
	return &cp
}

func (r *SessionRepository) Create(_ context.Context, session *domain.Session) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.sessions[session.ID] = r.copySession(session)
	return nil
}

func (r *SessionRepository) Get(_ context.Context, id domain.SessionID) (*domain.Session, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	s, ok := r.sessions[id]
	if !ok {
		return nil, domain.ErrSessionNotFound
	}
	return r.copySession(s), nil
}

func (r *SessionRepository) ListByUser(_ context.Context, userID domain.UserID) ([]domain.Session, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	now := time.Now()
	var result []domain.Session
	for _, s := range r.sessions {
		if s.UserID == userID && s.RevokedAt == nil && s.ExpiresAt.After(now) {
			result = append(result, *r.copySession(s))
		}
	}
	return result, nil
}

func (r *SessionRepository) UpdateLastActive(_ context.Context, id domain.SessionID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	s, ok := r.sessions[id]
	if !ok {
		return domain.ErrSessionNotFound
	}
	s.LastActiveAt = time.Now()
	return nil
}

func (r *SessionRepository) Revoke(_ context.Context, id domain.SessionID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	s, ok := r.sessions[id]
	if !ok {
		return domain.ErrSessionNotFound
	}
	now := time.Now()
	s.RevokedAt = &now
	return nil
}

func (r *SessionRepository) RevokeAllForUser(_ context.Context, userID domain.UserID, exceptID *domain.SessionID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now()
	for _, s := range r.sessions {
		if s.UserID == userID && s.RevokedAt == nil {
			if exceptID != nil && s.ID == *exceptID {
				continue
			}
			s.RevokedAt = &now
		}
	}
	return nil
}

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

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