package urlshortener

import (
	"errors"
	"sync"
)

var ErrNotFound = errors.New("url not found")

// URLMetrics holds data about how many times a short URL was hit.
type URLMetrics struct {
	ShortCode   string `json:"short_code"`
	OriginalURL string `json:"original_url"`
	Hits        int    `json:"hits"`
}

// URLStore defines the actions required to store and retrieve URL mappings.
type URLStore interface {
	Save(shortCode, originalURL string) error
	Get(shortCode string) (string, error)
	IncrementHits(shortCode string) error
	GetMetrics(shortCode string) (URLMetrics, error)
}

// MemoryStore is an in-memory implementation of URLStore.
// Note: In Go, read/write access to maps from multiple goroutines is not thread-safe.
// We must use sync.RWMutex to ensure our web server doesn't crash on concurrent traffic!
type MemoryStore struct {
	mu   sync.RWMutex
	urls map[string]string
	hits map[string]int
}

// NewMemoryStore initializes a new MemoryStore.
func NewMemoryStore() *MemoryStore {
	return &MemoryStore{
		urls: make(map[string]string),
		hits: make(map[string]int),
	}
}

// Save stores the link mapping.
func (m *MemoryStore) Save(shortCode, originalURL string) error {
	// TODO: Safely save mapping to the map. Use m.mu.Lock() / defer m.mu.Unlock().
	m.mu.Lock()
	defer m.mu.Unlock()
	m.urls[shortCode] = originalURL
	return nil
}

// Get retrieves the original URL. If not found, it must return ErrNotFound.
func (m *MemoryStore) Get(shortCode string) (string, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	val, ok := m.urls[shortCode]
	if !ok {
		return "", ErrNotFound
	}
	return val, nil
	// TODO: Safely read from map. Use m.mu.RLock() / defer m.mu.RUnlock().
}

// IncrementHits increases the hit counter by 1. If code not found, return ErrNotFound.
func (m *MemoryStore) IncrementHits(shortCode string) error {
	// TODO: Safely increment hits.
	m.mu.Lock()
	defer m.mu.Unlock()
	m.hits[shortCode]++
	return nil
}

// GetMetrics returns the redirection metrics for a short code.
func (m *MemoryStore) GetMetrics(shortCode string) (URLMetrics, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	val, ok := m.urls[shortCode]
	if !ok {
		return URLMetrics{}, ErrNotFound
	}
	return URLMetrics{ShortCode: shortCode, OriginalURL: val, Hits: m.hits[shortCode]}, nil
	// TODO: Safely retrieve and compile URLMetrics.
}
