package auth

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"net/http"

	"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/middleware"
	"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/permissions"
)

func generateToken() string {
	b := make([]byte, 32)
	rand.Read(b)
	return hex.EncodeToString(b)
}

func (h *Handler) RequestVerification(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return
	}

	user, err := h.getProfile(r.Context(), userID)
	if err != nil {
		http.Error(w, `{"error":"user not found"}`, http.StatusNotFound)
		return
	}

	token := generateToken()
	_, err = h.db.ExecContext(r.Context(), `
		INSERT INTO user_tokens (user_id, token, type, expires_at)
		VALUES ($1, $2, 'verify_email', NOW() + INTERVAL '24 hours')
	`, userID, token)
	if err != nil {
		http.Error(w, `{"error":"failed to generate token"}`, http.StatusInternalServerError)
		return
	}

	appURL := h.cfg.AppURL()

	if err := h.mailer.SendVerificationEmail(user.Email, user.Username, token, appURL); err != nil {
		http.Error(w, `{"error":"failed to send email"}`, http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) VerifyEmail(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Token string `json:"token"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
		return
	}

	var userID string
	err := h.db.QueryRowContext(r.Context(), `
		DELETE FROM user_tokens
		WHERE token = $1 AND type = 'verify_email' AND expires_at > NOW()
		RETURNING user_id
	`, req.Token).Scan(&userID)
	if err != nil {
		http.Error(w, `{"error":"invalid or expired token"}`, http.StatusBadRequest)
		return
	}

	_, err = h.db.ExecContext(r.Context(), `
		UPDATE users SET email_verified = TRUE WHERE id = $1
	`, userID)
	if err != nil {
		http.Error(w, `{"error":"failed to update user"}`, http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) RequestPasswordReset(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Email string `json:"email"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
		return
	}

	var userID, username string
	err := h.db.QueryRowContext(r.Context(), `
		SELECT id, username FROM users WHERE email = $1
	`, req.Email).Scan(&userID, &username)
	if err != nil {
		// Silent fail to prevent email enumeration
		w.WriteHeader(http.StatusNoContent)
		return
	}

	token := generateToken()
	_, err = h.db.ExecContext(r.Context(), `
		INSERT INTO user_tokens (user_id, token, type, expires_at)
		VALUES ($1, $2, 'reset_password', NOW() + INTERVAL '1 hour')
	`, userID, token)
	if err != nil {
		http.Error(w, `{"error":"failed to generate token"}`, http.StatusInternalServerError)
		return
	}

	appURL := h.cfg.AppURL()

	h.mailer.SendPasswordResetEmail(req.Email, username, token, appURL)
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Token       string `json:"token"`
		NewPassword string `json:"new_password"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
		return
	}
	if len(req.NewPassword) < 8 {
		http.Error(w, `{"error":"password too short"}`, http.StatusBadRequest)
		return
	}

	var userID string
	err := h.db.QueryRowContext(r.Context(), `
		DELETE FROM user_tokens
		WHERE token = $1 AND type = 'reset_password' AND expires_at > NOW()
		RETURNING user_id
	`, req.Token).Scan(&userID)
	if err != nil {
		http.Error(w, `{"error":"invalid or expired token"}`, http.StatusBadRequest)
		return
	}

	hash, err := HashPassword(req.NewPassword)
	if err != nil {
		http.Error(w, `{"error":"failed to hash password"}`, http.StatusInternalServerError)
		return
	}

	_, err = h.db.ExecContext(r.Context(), `
		UPDATE users SET password_hash = $2 WHERE id = $1
	`, userID, hash)
	if err != nil {
		http.Error(w, `{"error":"failed to update user"}`, http.StatusInternalServerError)
		return
	}
	
	// Delete all sessions to force re-login
	h.db.ExecContext(r.Context(), `DELETE FROM sessions WHERE user_id = $1`, userID)

	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) AdminAnnounce(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return
	}

	var req struct {
		Subject string `json:"subject"`
		Body    string `json:"body"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
		return
	}

	// Check if user is an admin across the whole system.
	// For now, let's assume if they have ADMINISTRATOR on ANY server, they can announce.
	// Or maybe there is a global admin flag? Let's check permissions.
	// Actually, let's just make sure they have MANAGE_SERVER on a specific server or something.
	// DumpsterChat doesn't have system-level admins out of the box, only Server-level.
	// Let's require them to be the owner of at least one server, or have ADMINISTRATOR on a server?
	// The prompt just said "admin sent announcements". Let's allow it for users who are in any server with ADMINISTRATOR.
	var isAdmin bool
	err := h.db.QueryRowContext(r.Context(), `
		SELECT EXISTS (
			SELECT 1 FROM member_roles mr
			JOIN roles r ON mr.role_id = r.id
			WHERE mr.user_id = $1 AND (r.permissions & $2) = $2
		)
	`, userID, permissions.ADMINISTRATOR).Scan(&isAdmin)
	
	if err != nil || !isAdmin {
		http.Error(w, `{"error":"must be an administrator"}`, http.StatusForbidden)
		return
	}

	rows, err := h.db.QueryContext(r.Context(), `SELECT email FROM users`)
	if err != nil {
		http.Error(w, `{"error":"failed to fetch users"}`, http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var email string
		if err := rows.Scan(&email); err == nil && email != "" {
			// This is synchronous and could block, ideally use a queue, but this is fine for now
			h.mailer.SendAnnouncement(email, req.Subject, req.Body)
		}
	}

	w.WriteHeader(http.StatusNoContent)
}
