package handler

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"net/http"
	"sync"

	"golang.org/x/crypto/bcrypt"

	"podstalk/store"
)

// Simple in-memory session store.
type SessionStore struct {
	mu       sync.Mutex
	sessions map[string]int64 // token -> userID
}

func NewSessionStore() *SessionStore {
	return &SessionStore{sessions: make(map[string]int64)}
}

func (ss *SessionStore) Create(userID int64) string {
	token := make([]byte, 32)
	rand.Read(token)
	key := hex.EncodeToString(token)
	ss.mu.Lock()
	ss.sessions[key] = userID
	ss.mu.Unlock()
	return key
}

func (ss *SessionStore) Get(token string) (int64, bool) {
	ss.mu.Lock()
	defer ss.mu.Unlock()
	id, ok := ss.sessions[token]
	return id, ok
}

func (ss *SessionStore) Delete(token string) {
	ss.mu.Lock()
	delete(ss.sessions, token)
	ss.mu.Unlock()
}

// --- Handlers ---

type AuthHandler struct {
	Store    *store.Store
	Sessions *SessionStore
	Tpl      *Templates
}

func (h *AuthHandler) ServeSignup(w http.ResponseWriter, r *http.Request) {
	count, err := h.Store.UserCount()
	if err == nil && count > 0 {
		http.Redirect(w, r, "/signin", http.StatusSeeOther)
		return
	}

	if r.Method == http.MethodGet {
		h.Tpl.Render(w, "signup", nil)
		return
	}

	if r.Method == http.MethodPost {
		email := r.FormValue("email")
		password := r.FormValue("password")

		if email == "" || password == "" {
			h.Tpl.Render(w, "signup", map[string]string{"Error": "Email and password are required."})
			return
		}

		hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
		if err != nil {
			h.Tpl.Render(w, "signup", map[string]string{"Error": "Internal error."})
			return
		}

		userID, err := h.Store.CreateUser(email, string(hash))
		if err != nil {
			h.Tpl.Render(w, "signup", map[string]string{"Error": "A user with that email already exists."})
			return
		}

		token := h.Sessions.Create(userID)
		http.SetCookie(w, &http.Cookie{
			Name:     "session",
			Value:    token,
			Path:     "/",
			HttpOnly: true,
		})
		http.Redirect(w, r, "/", http.StatusSeeOther)
	}
}

func (h *AuthHandler) hasUsers() bool {
	count, err := h.Store.UserCount()
	return err == nil && count > 0
}

func (h *AuthHandler) ServeSignin(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		h.Tpl.Render(w, "signin", map[string]any{"HasUser": h.hasUsers()})
		return
	}

	if r.Method == http.MethodPost {
		email := r.FormValue("email")
		password := r.FormValue("password")

		user, err := h.Store.UserByEmail(email)
		if err != nil {
			h.Tpl.Render(w, "signin", map[string]any{"Error": "Invalid email or password.", "HasUser": h.hasUsers()})
			return
		}

		if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
			h.Tpl.Render(w, "signin", map[string]any{"Error": "Invalid email or password.", "HasUser": h.hasUsers()})
			return
		}

		token := h.Sessions.Create(user.ID)
		http.SetCookie(w, &http.Cookie{
			Name:     "session",
			Value:    token,
			Path:     "/",
			HttpOnly: true,
		})
		http.Redirect(w, r, "/", http.StatusSeeOther)
	}
}

func (h *AuthHandler) ServeSignout(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("session")
	if err == nil {
		h.Sessions.Delete(cookie.Value)
	}
	http.SetCookie(w, &http.Cookie{
		Name:   "session",
		Value:  "",
		Path:   "/",
		MaxAge: -1,
	})
	http.Redirect(w, r, "/signin", http.StatusSeeOther)
}

type contextKey string

const userIDKey contextKey = "userID"

// UserIDFromContext extracts the authenticated user's ID from the request context.
func UserIDFromContext(r *http.Request) int64 {
	id, _ := r.Context().Value(userIDKey).(int64)
	return id
}

type AuthMiddleware struct {
	Store    *store.Store
	Sessions *SessionStore
}

func (am *AuthMiddleware) RequireAuth(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		cookie, err := r.Cookie("session")
		if err != nil {
			http.Redirect(w, r, "/signin", http.StatusSeeOther)
			return
		}
		userID, ok := am.Sessions.Get(cookie.Value)
		if !ok {
			http.Redirect(w, r, "/signin", http.StatusSeeOther)
			return
		}
		ctx := context.WithValue(r.Context(), userIDKey, userID)
		next(w, r.WithContext(ctx))
	}
}

// RedirectIfAuthed sends authenticated users to / instead of showing auth pages.
func (am *AuthMiddleware) RedirectIfAuthed(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		cookie, err := r.Cookie("session")
		if err == nil {
			if _, ok := am.Sessions.Get(cookie.Value); ok {
				http.Redirect(w, r, "/", http.StatusSeeOther)
				return
			}
		}
		next(w, r)
	}
}
