package bot

import (
	"database/sql"
	"encoding/json"
	"errors"
	"net/http"

	"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/middleware"
	"github.com/go-chi/chi/v5"
)

// Handler handles bot CRUD and server-assignment routes.
type Handler struct {
	db *sql.DB
}

// NewHandler creates a new bot Handler.
func NewHandler(db *sql.DB) *Handler {
	return &Handler{db: db}
}

// RegisterRoutes registers authenticated bot routes under the given router.
func (h *Handler) RegisterRoutes(r chi.Router) {
	r.Post("/", h.Create)
	r.Get("/", h.List)
	r.Get("/{botID}", h.Get)
	r.Patch("/{botID}", h.Update)
	r.Delete("/{botID}", h.Delete)
	r.Post("/{botID}/servers", h.AddToServer)
	r.Delete("/{botID}/servers/{serverID}", h.RemoveFromServer)
	r.Post("/{botID}/regenerate-token", h.RegenerateToken)
}

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

type botResponse struct {
	ID          string  `json:"id"`
	Name        string  `json:"name"`
	Avatar      *string `json:"avatar"`
	Description string  `json:"description"`
	OwnerID     string  `json:"owner_id"`
	CreatedAt   string  `json:"created_at"`
}

// botWithToken is returned only on create / regenerate-token.
type botWithToken struct {
	botResponse
	Token string `json:"token"`
}

type createBotRequest struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

type updateBotRequest struct {
	Name        *string `json:"name"`
	Description *string `json:"description"`
	Avatar      *string `json:"avatar"`
}

type addToServerRequest struct {
	ServerID string `json:"server_id"`
}

// ---- helpers ----

func writeJSON(w http.ResponseWriter, status int, v interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(v)
}

func writeErr(w http.ResponseWriter, status int, msg string) {
	http.Error(w, `{"error":"`+msg+`"}`, status)
}

// ---- handlers ----

// @Summary Create a bot
// @Description Create a new bot
// @Tags bots
// @Accept json
// @Produce json
// @Security SessionAuth
// @Param body body createBotRequest true "Bot data"
// @Success 201 {object} botWithToken
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Router /bots [post]
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		writeErr(w, http.StatusUnauthorized, "unauthorized")
		return
	}

	var req createBotRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErr(w, http.StatusBadRequest, "invalid request")
		return
	}
	if req.Name == "" {
		writeErr(w, http.StatusBadRequest, "name is required")
		return
	}

	token := GenerateToken()
	tokenHash := HashToken(token)

	var bot botWithToken
	var avatar sql.NullString
	var createdAt sql.NullString
	err := h.db.QueryRowContext(r.Context(), `
		INSERT INTO bots (name, description, owner_id, token)
		VALUES ($1, $2, $3, $4)
		RETURNING id, name, avatar, description, owner_id, created_at::text
	`, req.Name, req.Description, userID, tokenHash).Scan(
		&bot.ID, &bot.Name, &avatar, &bot.Description, &bot.OwnerID, &createdAt,
	)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "failed to create bot")
		return
	}
	if avatar.Valid {
		bot.Avatar = &avatar.String
	}
	bot.CreatedAt = createdAt.String
	bot.Token = token

	writeJSON(w, http.StatusCreated, bot)
}

// @Summary List bots
// @Description List all bots owned by the current user
// @Tags bots
// @Produce json
// @Security SessionAuth
// @Success 200 {array} botResponse
// @Failure 401 {object} map[string]string
// @Router /bots [get]
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		writeErr(w, http.StatusUnauthorized, "unauthorized")
		return
	}

	rows, err := h.db.QueryContext(r.Context(), `
		SELECT id, name, avatar, description, owner_id, created_at::text
		FROM bots WHERE owner_id = $1 ORDER BY created_at DESC
	`, userID)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "server error")
		return
	}
	defer rows.Close()

	bots := make([]botResponse, 0)
	for rows.Next() {
		var b botResponse
		var avatar sql.NullString
		var createdAt sql.NullString
		if err := rows.Scan(&b.ID, &b.Name, &avatar, &b.Description, &b.OwnerID, &createdAt); err != nil {
			writeErr(w, http.StatusInternalServerError, "server error")
			return
		}
		if avatar.Valid {
			b.Avatar = &avatar.String
		}
		b.CreatedAt = createdAt.String
		bots = append(bots, b)
	}
	if err := rows.Err(); err != nil {
		writeErr(w, http.StatusInternalServerError, "server error")
		return
	}

	writeJSON(w, http.StatusOK, bots)
}

// @Summary Get a bot
// @Description Get a bot by ID (owner only)
// @Tags bots
// @Produce json
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Success 200 {object} botResponse
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /bots/{botID} [get]
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		writeErr(w, http.StatusUnauthorized, "unauthorized")
		return
	}

	botID := chi.URLParam(r, "botID")

	var b botResponse
	var avatar sql.NullString
	var createdAt sql.NullString
	err := h.db.QueryRowContext(r.Context(), `
		SELECT id, name, avatar, description, owner_id, created_at::text
		FROM bots WHERE id = $1
	`, botID).Scan(&b.ID, &b.Name, &avatar, &b.Description, &b.OwnerID, &createdAt)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}
	if b.OwnerID != userID {
		writeErr(w, http.StatusNotFound, "bot not found")
		return
	}
	if avatar.Valid {
		b.Avatar = &avatar.String
	}
	b.CreatedAt = createdAt.String

	writeJSON(w, http.StatusOK, b)
}

// @Summary Update a bot
// @Description Update a bot's name, description, or avatar (owner only)
// @Tags bots
// @Accept json
// @Produce json
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Param body body updateBotRequest true "Bot update data"
// @Success 200 {object} botResponse
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /bots/{botID} [patch]
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		writeErr(w, http.StatusUnauthorized, "unauthorized")
		return
	}

	botID := chi.URLParam(r, "botID")

	// Verify ownership
	var ownerID string
	err := h.db.QueryRowContext(r.Context(), `
		SELECT owner_id FROM bots WHERE id = $1
	`, botID).Scan(&ownerID)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}
	if ownerID != userID {
		writeErr(w, http.StatusNotFound, "bot not found")
		return
	}

	var req updateBotRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErr(w, http.StatusBadRequest, "invalid request")
		return
	}
	if req.Name == nil && req.Description == nil && req.Avatar == nil {
		writeErr(w, http.StatusBadRequest, "nothing to update")
		return
	}

	var b botResponse
	var avatar sql.NullString
	var createdAt sql.NullString
	err = h.db.QueryRowContext(r.Context(), `
		UPDATE bots
		SET name = COALESCE($1, name),
			description = COALESCE($2, description),
			avatar = COALESCE($3, avatar)
		WHERE id = $4
		RETURNING id, name, avatar, description, owner_id, created_at::text
	`, req.Name, req.Description, req.Avatar, botID).Scan(
		&b.ID, &b.Name, &avatar, &b.Description, &b.OwnerID, &createdAt,
	)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "failed to update bot")
		return
	}
	if avatar.Valid {
		b.Avatar = &avatar.String
	}
	b.CreatedAt = createdAt.String

	writeJSON(w, http.StatusOK, b)
}

// @Summary Delete a bot
// @Description Delete a bot (owner only)
// @Tags bots
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Success 204
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /bots/{botID} [delete]
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		writeErr(w, http.StatusUnauthorized, "unauthorized")
		return
	}

	botID := chi.URLParam(r, "botID")

	var ownerID string
	err := h.db.QueryRowContext(r.Context(), `
		SELECT owner_id FROM bots WHERE id = $1
	`, botID).Scan(&ownerID)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}
	if ownerID != userID {
		writeErr(w, http.StatusNotFound, "bot not found")
		return
	}

	_, err = h.db.ExecContext(r.Context(), `DELETE FROM bots WHERE id = $1`, botID)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "failed to delete bot")
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// @Summary Add bot to server
// @Description Add a bot to a server
// @Tags bots
// @Accept json
// @Produce json
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Param body body addToServerRequest true "Server data"
// @Success 201 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /bots/{botID}/servers [post]
func (h *Handler) AddToServer(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		writeErr(w, http.StatusUnauthorized, "unauthorized")
		return
	}

	botID := chi.URLParam(r, "botID")

	// Verify bot ownership
	var ownerID string
	err := h.db.QueryRowContext(r.Context(), `
		SELECT owner_id FROM bots WHERE id = $1
	`, botID).Scan(&ownerID)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}
	if ownerID != userID {
		writeErr(w, http.StatusNotFound, "bot not found")
		return
	}

	var req addToServerRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErr(w, http.StatusBadRequest, "invalid request")
		return
	}
	if req.ServerID == "" {
		writeErr(w, http.StatusBadRequest, "server_id is required")
		return
	}

	// Verify user is a member of the target server
	var exists bool
	err = h.db.QueryRowContext(r.Context(), `
		SELECT EXISTS(SELECT 1 FROM members WHERE user_id = $1 AND server_id = $2)
	`, userID, req.ServerID).Scan(&exists)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "server error")
		return
	}
	if !exists {
		writeErr(w, http.StatusForbidden, "not a member of this server")
		return
	}

	_, err = h.db.ExecContext(r.Context(), `
		INSERT INTO bot_servers (bot_id, server_id, added_by)
		VALUES ($1, $2, $3)
		ON CONFLICT (bot_id, server_id) DO NOTHING
	`, botID, req.ServerID, userID)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "failed to add bot to server")
		return
	}

	writeJSON(w, http.StatusCreated, map[string]string{
		"bot_id":    botID,
		"server_id": req.ServerID,
	})
}

// @Summary Remove bot from server
// @Description Remove a bot from a server
// @Tags bots
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Param serverID path string true "Server ID"
// @Success 204
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /bots/{botID}/servers/{serverID} [delete]
func (h *Handler) RemoveFromServer(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		writeErr(w, http.StatusUnauthorized, "unauthorized")
		return
	}

	botID := chi.URLParam(r, "botID")
	serverID := chi.URLParam(r, "serverID")

	// Verify bot ownership
	var ownerID string
	err := h.db.QueryRowContext(r.Context(), `
		SELECT owner_id FROM bots WHERE id = $1
	`, botID).Scan(&ownerID)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}
	if ownerID != userID {
		writeErr(w, http.StatusNotFound, "bot not found")
		return
	}

	result, err := h.db.ExecContext(r.Context(), `
		DELETE FROM bot_servers WHERE bot_id = $1 AND server_id = $2
	`, botID, serverID)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "failed to remove bot from server")
		return
	}
	rows, _ := result.RowsAffected()
	if rows == 0 {
		writeErr(w, http.StatusNotFound, "bot not in this server")
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// @Summary Regenerate bot token
// @Description Regenerate a bot's API token (owner only)
// @Tags bots
// @Produce json
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Success 200 {object} botWithToken
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /bots/{botID}/regenerate-token [post]
func (h *Handler) RegenerateToken(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		writeErr(w, http.StatusUnauthorized, "unauthorized")
		return
	}

	botID := chi.URLParam(r, "botID")

	// Verify ownership
	var ownerID string
	err := h.db.QueryRowContext(r.Context(), `
		SELECT owner_id FROM bots WHERE id = $1
	`, botID).Scan(&ownerID)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}
	if ownerID != userID {
		writeErr(w, http.StatusNotFound, "bot not found")
		return
	}

	token := GenerateToken()
	tokenHash := HashToken(token)

	var b botWithToken
	var avatar sql.NullString
	var createdAt sql.NullString
	err = h.db.QueryRowContext(r.Context(), `
		UPDATE bots SET token = $1
		WHERE id = $2
		RETURNING id, name, avatar, description, owner_id, created_at::text
	`, tokenHash, botID).Scan(
		&b.ID, &b.Name, &avatar, &b.Description, &b.OwnerID, &createdAt,
	)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "failed to regenerate token")
		return
	}
	if avatar.Valid {
		b.Avatar = &avatar.String
	}
	b.CreatedAt = createdAt.String
	b.Token = token

	writeJSON(w, http.StatusOK, b)
}
