package bot

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

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

// CommandHandler handles slash command CRUD routes.
type CommandHandler struct {
	db *sql.DB
}

// NewCommandHandler creates a new CommandHandler.
func NewCommandHandler(db *sql.DB) *CommandHandler {
	return &CommandHandler{db: db}
}

// RegisterCommandRoutes registers slash-command routes under the given router.
// Authenticated routes (bot-scoped):
//
//	POST   /{botID}/commands            - create slash command
//	GET    /{botID}/commands            - list bot's commands
//	DELETE /{botID}/commands/{cmdID}    - delete command
//
// Public route (for autocomplete):
//
//	GET    /servers/{serverID}/commands  - list all commands for a server
func (h *CommandHandler) RegisterCommandRoutes(r chi.Router) {
	r.Post("/{botID}/commands", h.Create)
	r.Get("/{botID}/commands", h.ListByBot)
	r.Delete("/{botID}/commands/{cmdID}", h.Delete)
	r.Get("/servers/{serverID}/commands", h.ListByServer)
}

// ---- types ----

type slashCommandResponse struct {
	ID          string      `json:"id"`
	BotID       string      `json:"bot_id"`
	ServerID    string      `json:"server_id"`
	Name        string      `json:"name"`
	Description string      `json:"description"`
	Options     interface{} `json:"options"`
	CreatedAt   string      `json:"created_at"`
}

type createCommandRequest struct {
	ServerID    string      `json:"server_id"`
	Name        string      `json:"name"`
	Description string      `json:"description"`
	Options     interface{} `json:"options"`
}

// ---- handlers ----

// @Summary Create a slash command
// @Description Create a new slash command for a bot
// @Tags bot commands
// @Accept json
// @Produce json
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Param body body createCommandRequest true "Command data"
// @Success 201 {object} slashCommandResponse
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Router /bots/{botID}/commands [post]
func (h *CommandHandler) Create(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
	if err := h.verifyBotOwnership(r, userID, botID); err != nil {
		if err == errNotFound {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else if err == errForbidden {
			writeErr(w, http.StatusForbidden, "forbidden")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}

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

	// Verify bot is added to the target server
	added, err := h.isBotOnServer(r, botID, req.ServerID)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "server error")
		return
	}
	if !added {
		writeErr(w, http.StatusForbidden, "bot is not added to this server")
		return
	}

	// Marshal options to JSON for storage
	optionsJSON := "[]"
	if req.Options != nil {
		b, err := json.Marshal(req.Options)
		if err != nil {
			writeErr(w, http.StatusBadRequest, "invalid options")
			return
		}
		optionsJSON = string(b)
	}

	var cmd slashCommandResponse
	var createdAt sql.NullString
	err = h.db.QueryRowContext(r.Context(), `
		INSERT INTO slash_commands (bot_id, server_id, name, description, options)
		VALUES ($1, $2, $3, $4, $5::jsonb)
		RETURNING id, bot_id, server_id, name, description, options, created_at::text
	`, botID, req.ServerID, req.Name, req.Description, optionsJSON).Scan(
		&cmd.ID, &cmd.BotID, &cmd.ServerID, &cmd.Name, &cmd.Description,
		&cmd.Options, &createdAt,
	)
	if err != nil {
		// Likely a UNIQUE(server_id, name) violation
		writeErr(w, http.StatusConflict, "command name already exists in this server")
		return
	}
	cmd.CreatedAt = createdAt.String

	writeJSON(w, http.StatusCreated, cmd)
}

// @Summary List bot commands
// @Description List all slash commands for a bot
// @Tags bot commands
// @Produce json
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Success 200 {array} slashCommandResponse
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /bots/{botID}/commands [get]
func (h *CommandHandler) ListByBot(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")

	if err := h.verifyBotOwnership(r, userID, botID); err != nil {
		if err == errNotFound {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else if err == errForbidden {
			writeErr(w, http.StatusForbidden, "forbidden")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}

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

	cmds := make([]slashCommandResponse, 0)
	for rows.Next() {
		var cmd slashCommandResponse
		var createdAt sql.NullString
		if err := rows.Scan(&cmd.ID, &cmd.BotID, &cmd.ServerID, &cmd.Name, &cmd.Description, &cmd.Options, &createdAt); err != nil {
			writeErr(w, http.StatusInternalServerError, "server error")
			return
		}
		cmd.CreatedAt = createdAt.String
		cmds = append(cmds, cmd)
	}
	if err := rows.Err(); err != nil {
		writeErr(w, http.StatusInternalServerError, "server error")
		return
	}

	writeJSON(w, http.StatusOK, cmds)
}

// @Summary Delete a slash command
// @Description Delete a slash command
// @Tags bot commands
// @Security SessionAuth
// @Param botID path string true "Bot ID"
// @Param cmdID path string true "Command ID"
// @Success 204
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /bots/{botID}/commands/{cmdID} [delete]
func (h *CommandHandler) 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")
	cmdID := chi.URLParam(r, "cmdID")

	if err := h.verifyBotOwnership(r, userID, botID); err != nil {
		if err == errNotFound {
			writeErr(w, http.StatusNotFound, "bot not found")
		} else if err == errForbidden {
			writeErr(w, http.StatusForbidden, "forbidden")
		} else {
			writeErr(w, http.StatusInternalServerError, "server error")
		}
		return
	}

	result, err := h.db.ExecContext(r.Context(), `
		DELETE FROM slash_commands WHERE id = $1 AND bot_id = $2
	`, cmdID, botID)
	if err != nil {
		writeErr(w, http.StatusInternalServerError, "failed to delete command")
		return
	}
	rows, _ := result.RowsAffected()
	if rows == 0 {
		writeErr(w, http.StatusNotFound, "command not found")
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// @Summary List server commands
// @Description List all slash commands registered in a server (public)
// @Tags bot commands
// @Produce json
// @Param serverID path string true "Server ID"
// @Success 200 {array} slashCommandResponse
// @Failure 500 {object} map[string]string
// @Router /bots/servers/{serverID}/commands [get]
func (h *CommandHandler) ListByServer(w http.ResponseWriter, r *http.Request) {
	serverID := chi.URLParam(r, "serverID")

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

	cmds := make([]slashCommandResponse, 0)
	for rows.Next() {
		var cmd slashCommandResponse
		var createdAt sql.NullString
		if err := rows.Scan(&cmd.ID, &cmd.BotID, &cmd.ServerID, &cmd.Name, &cmd.Description, &cmd.Options, &createdAt); err != nil {
			writeErr(w, http.StatusInternalServerError, "server error")
			return
		}
		cmd.CreatedAt = createdAt.String
		cmds = append(cmds, cmd)
	}
	if err := rows.Err(); err != nil {
		writeErr(w, http.StatusInternalServerError, "server error")
		return
	}

	writeJSON(w, http.StatusOK, cmds)
}

// ---- internal helpers ----

var (
	errNotFound  = errors.New("not found")
	errForbidden = errors.New("forbidden")
)

func (h *CommandHandler) verifyBotOwnership(r *http.Request, userID, botID string) error {
	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) {
			return errNotFound
		}
		return err
	}
	if ownerID != userID {
		return errNotFound
	}
	return nil
}

func (h *CommandHandler) isBotOnServer(r *http.Request, botID, serverID string) (bool, error) {
	var exists bool
	err := h.db.QueryRowContext(r.Context(), `
		SELECT EXISTS(SELECT 1 FROM bot_servers WHERE bot_id = $1 AND server_id = $2)
	`, botID, serverID).Scan(&exists)
	return exists, err
}
