package api

import (
	"context"
	"encoding/json"
	"net/http"
	"time"

	"wikiapiserver/db"
)

const defaultTimeout = 5 * time.Second

// Handler holds the DB dependency for all HTTP handlers.
type Handler struct {
	db *db.DB
}

// NewHandler creates a Handler backed by the given DB.
func NewHandler(database *db.DB) *Handler {
	return &Handler{db: database}
}

// --- request/response types ---

type errResp struct {
	Error string `json:"error"`
}

type registerReq struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type loginReq struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type refreshReq struct {
	RefreshToken string `json:"refresh_token"`
}

// --- helper writers ---

func writeJSON(w http.ResponseWriter, code int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(v) //nolint:errcheck
}

func badRequest(w http.ResponseWriter, msg string) {
	writeJSON(w, http.StatusBadRequest, errResp{Error: msg})
}

func unauthorized(w http.ResponseWriter) {
	writeJSON(w, http.StatusUnauthorized, errResp{Error: "unauthorized"})
}

func serverError(w http.ResponseWriter, msg string) {
	writeJSON(w, http.StatusInternalServerError, errResp{Error: msg})
}

// --- Register: POST /register ---

func (h *Handler) Register(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), defaultTimeout)
	defer cancel()

	var req registerReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		badRequest(w, "invalid JSON")
		return
	}

	if req.Username == "" || req.Password == "" {
		badRequest(w, "username and password are required")
		return
	}

	acct, err := h.db.CreateAccount(ctx, req.Username, req.Password)
	if err != nil {
		if err.Error() == "username already exists" {
			badRequest(w, "username already exists")
			return
		}
		serverError(w, "could not create account")
		return
	}

	writeJSON(w, http.StatusCreated, acct)
}

// --- Login: POST /login ---

func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), defaultTimeout)
	defer cancel()

	var req loginReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		badRequest(w, "invalid JSON")
		return
	}

	if req.Username == "" || req.Password == "" {
		badRequest(w, "username and password are required")
		return
	}

	acct, err := h.db.Authenticate(ctx, req.Username, req.Password)
	if err != nil {
		if err.Error() == "invalid credentials" {
			unauthorized(w)
			return
		}
		serverError(w, "authentication failed")
		return
	}

	writeJSON(w, http.StatusOK, acct)
}

// --- Refresh: POST /refresh ---

func (h *Handler) Refresh(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), defaultTimeout)
	defer cancel()

	var req refreshReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		badRequest(w, "invalid JSON")
		return
	}

	if req.RefreshToken == "" {
		badRequest(w, "refresh_token is required")
		return
	}

	acct, err := h.db.RefreshByToken(ctx, req.RefreshToken)
	if err != nil {
		if err.Error() == "invalid refresh token" {
			unauthorized(w)
			return
		}
		serverError(w, "could not refresh token")
		return
	}

	writeJSON(w, http.StatusOK, acct)
}

// --- Health: GET /health ---

func (h *Handler) Health(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), defaultTimeout)
	defer cancel()

	if err := h.db.Ping(ctx); err != nil {
		writeJSON(w, http.StatusServiceUnavailable, errResp{Error: "database unavailable"})
		return
	}

	writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}
