package auth

import (
	"crypto/rand"
	"encoding/hex"
	"log/slog"
	"net/http"
	"sync"
	"time"
)

// Session represents an authenticated user session.
type Session struct {
	Token     string
	ExpiresAt time.Time
}

// Manager handles authentication and session management.
type Manager struct {
	mu        sync.RWMutex
	sessions  map[string]*Session
	username  string
	password  string
	expiryDur time.Duration
}

// New creates a new authentication manager with default credentials.
func New(username, password string) *Manager {
	return &Manager{
		sessions:  make(map[string]*Session),
		username:  username,
		password:  password,
		expiryDur: 24 * time.Hour,
	}
}

// Login validates credentials and creates a session.
func (m *Manager) Login(username, password string) (string, error) {
	if username != m.username || password != m.password {
		slog.Debug("Login failed - invalid credentials", "username", username)
		return "", ErrInvalidCredentials
	}

	token, err := generateToken()
	if err != nil {
		slog.Error("Failed to generate session token", "error", err)
		return "", err
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	m.sessions[token] = &Session{
		Token:     token,
		ExpiresAt: time.Now().Add(m.expiryDur),
	}

	slog.Debug("Login successful, session created", "username", username, "token", token[:8]+"...")
	return token, nil
}

// Validate checks if a session token is valid.
func (m *Manager) Validate(token string) bool {
	m.mu.RLock()
	defer m.mu.RUnlock()

	session, ok := m.sessions[token]
	if !ok {
		slog.Debug("Session validation failed - token not found", "token", token[:8]+"...")
		return false
	}

	if !session.ExpiresAt.After(time.Now()) {
		slog.Debug("Session validation failed - token expired", "token", token[:8]+"...", "expiredAt", session.ExpiresAt)
		return false
	}

	slog.Debug("Session validation successful", "token", token[:8]+"...")
	return true
}

// Logout invalidates a session.
func (m *Manager) Logout(token string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	delete(m.sessions, token)
	slog.Debug("Session logged out", "token", token[:8]+"...")
}

// Middleware returns a Chi middleware for authentication.
func (m *Manager) Middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		cookie, err := r.Cookie("session_token")
		if err != nil {
			slog.Debug("Authentication failed - no session cookie", "path", r.URL.Path, "remoteAddr", r.RemoteAddr)
			http.Redirect(w, r, "/login", http.StatusSeeOther)
			return
		}
		if !m.Validate(cookie.Value) {
			slog.Debug("Authentication failed - invalid session", "path", r.URL.Path, "remoteAddr", r.RemoteAddr)
			http.Redirect(w, r, "/login", http.StatusSeeOther)
			return
		}
		slog.Debug("Authentication successful", "path", r.URL.Path, "remoteAddr", r.RemoteAddr)
		next.ServeHTTP(w, r)
	})
}

// generateToken creates a random hex token.
func generateToken() (string, error) {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}
