package server

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

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

// RoleHandler manages role CRUD and member-role assignment.
type RoleHandler struct {
	db      *sql.DB
	checker *permissions.Checker
}

// NewRoleHandler creates a new RoleHandler.
func NewRoleHandler(db *sql.DB) *RoleHandler {
	return &RoleHandler{
		db:      db,
		checker: permissions.NewChecker(db),
	}
}

// RegisterRoleRoutes mounts role and member-role endpoints on the given router.
func (h *RoleHandler) RegisterRoleRoutes(r chi.Router) {
	r.Route("/servers/{serverID}/roles", func(r chi.Router) {
		r.Use(middleware.RequireAuth)
		r.Post("/", h.CreateRole)
		r.Get("/", h.ListRoles)
		r.Patch("/{roleID}", h.UpdateRole)
		r.Delete("/{roleID}", h.DeleteRole)
	})

	r.Route("/servers/{serverID}/members/{userID}/roles", func(r chi.Router) {
		r.Use(middleware.RequireAuth)
		r.Put("/", h.SetMemberRoles)
		r.Get("/", h.GetMemberRoles)
	})
}

// ---------------------------------------------------------------------------
// Request / response types
// ---------------------------------------------------------------------------

type createRoleRequest struct {
	Name        string `json:"name"`
	Color       string `json:"color"`
	Permissions int64  `json:"permissions"`
	Position    int    `json:"position"`
}

type updateRoleRequest struct {
	Name        *string `json:"name"`
	Color       *string `json:"color"`
	Permissions *int64  `json:"permissions"`
	Position    *int    `json:"position"`
}

type roleResponse struct {
	ID          string  `json:"id"`
	ServerID    string  `json:"server_id"`
	Name        string  `json:"name"`
	Color       *string `json:"color"`
	Permissions int64   `json:"permissions"`
	Position    int     `json:"position"`
	IsDefault   bool    `json:"is_default"`
	CreatedAt   string  `json:"created_at"`
}

type setMemberRolesRequest struct {
	RoleIDs []string `json:"role_ids"`
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

// requireManageServer checks that the caller has MANAGE_SERVER permission.
// Returns false (and writes the error response) if denied.
func (h *RoleHandler) requireManageServer(w http.ResponseWriter, r *http.Request, serverID string) bool {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return false
	}

	allowed, err := h.checker.CheckPermission(r.Context(), serverID, userID, permissions.MANAGE_SERVER)
	if err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return false
	}
	if !allowed {
		http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
		return false
	}
	return true
}

// scanRole scans a single role row into a roleResponse.
func scanRole(row interface{ Scan(dest ...any) error }) (roleResponse, error) {
	var role roleResponse
	err := row.Scan(
		&role.ID, &role.ServerID, &role.Name, &role.Color,
		&role.Permissions, &role.Position, &role.IsDefault, &role.CreatedAt,
	)
	return role, err
}

// ---------------------------------------------------------------------------
// Handlers
// ---------------------------------------------------------------------------

// CreateRole handles POST /servers/{serverID}/roles.
func (h *RoleHandler) CreateRole(w http.ResponseWriter, r *http.Request) {
	serverID := chi.URLParam(r, "serverID")
	if !h.requireManageServer(w, r, serverID) {
		return
	}

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

	role, err := scanRole(h.db.QueryRowContext(r.Context(), `
		INSERT INTO roles (server_id, name, color, permissions, position)
		VALUES ($1, $2, $3, $4, $5)
		RETURNING id, server_id, name, color, permissions, position, is_default, created_at
	`, serverID, req.Name, req.Color, req.Permissions, req.Position))
	if err != nil {
		http.Error(w, `{"error":"failed to create role"}`, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(role)
}

// ListRoles handles GET /servers/{serverID}/roles.
func (h *RoleHandler) ListRoles(w http.ResponseWriter, r *http.Request) {
	serverID := chi.URLParam(r, "serverID")

	rows, err := h.db.QueryContext(r.Context(), `
		SELECT id, server_id, name, color, permissions, position, is_default, created_at
		FROM roles
		WHERE server_id = $1
		ORDER BY position DESC, name
	`, serverID)
	if err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	roles := make([]roleResponse, 0)
	for rows.Next() {
		role, err := scanRole(rows)
		if err != nil {
			http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
			return
		}
		roles = append(roles, role)
	}
	if err := rows.Err(); err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(roles)
}

// UpdateRole handles PATCH /servers/{serverID}/roles/{roleID}.
func (h *RoleHandler) UpdateRole(w http.ResponseWriter, r *http.Request) {
	serverID := chi.URLParam(r, "serverID")
	roleID := chi.URLParam(r, "roleID")
	if !h.requireManageServer(w, r, serverID) {
		return
	}

	var req updateRoleRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
		return
	}
	if req.Name == nil && req.Color == nil && req.Permissions == nil && req.Position == nil {
		http.Error(w, `{"error":"nothing to update"}`, http.StatusBadRequest)
		return
	}

	role, err := scanRole(h.db.QueryRowContext(r.Context(), `
		UPDATE roles
		SET name = COALESCE($1, name),
			color = COALESCE($2, color),
			permissions = COALESCE($3, permissions),
			position = COALESCE($4, position)
		WHERE id = $5 AND server_id = $6
		RETURNING id, server_id, name, color, permissions, position, is_default, created_at
	`, req.Name, req.Color, req.Permissions, req.Position, roleID, serverID))
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			http.Error(w, `{"error":"role not found"}`, http.StatusNotFound)
			return
		}
		http.Error(w, `{"error":"failed to update role"}`, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(role)
}

// DeleteRole handles DELETE /servers/{serverID}/roles/{roleID}.
func (h *RoleHandler) DeleteRole(w http.ResponseWriter, r *http.Request) {
	serverID := chi.URLParam(r, "serverID")
	roleID := chi.URLParam(r, "roleID")
	if !h.requireManageServer(w, r, serverID) {
		return
	}

	// Prevent deleting the @everyone (default) role.
	var isDefault bool
	err := h.db.QueryRowContext(r.Context(), `
		SELECT is_default FROM roles WHERE id = $1 AND server_id = $2
	`, roleID, serverID).Scan(&isDefault)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			http.Error(w, `{"error":"role not found"}`, http.StatusNotFound)
			return
		}
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}
	if isDefault {
		http.Error(w, `{"error":"cannot delete the default @everyone role"}`, http.StatusBadRequest)
		return
	}

	_, err = h.db.ExecContext(r.Context(), `
		DELETE FROM roles WHERE id = $1 AND server_id = $2
	`, roleID, serverID)
	if err != nil {
		http.Error(w, `{"error":"failed to delete role"}`, http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// SetMemberRoles handles PUT /servers/{serverID}/members/{userID}/roles.
func (h *RoleHandler) SetMemberRoles(w http.ResponseWriter, r *http.Request) {
	serverID := chi.URLParam(r, "serverID")
	targetUserID := chi.URLParam(r, "userID")
	if !h.requireManageServer(w, r, serverID) {
		return
	}

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

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

	tx, err := h.db.BeginTx(r.Context(), nil)
	if err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}
	defer tx.Rollback()

	// Remove existing roles for this member in this server.
	_, err = tx.ExecContext(r.Context(), `
		DELETE FROM member_roles WHERE user_id = $1 AND server_id = $2
	`, targetUserID, serverID)
	if err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}

	// Insert new roles (skip the default role — it's implicit).
	for _, roleID := range req.RoleIDs {
		_, err = tx.ExecContext(r.Context(), `
			INSERT INTO member_roles (user_id, server_id, role_id)
			VALUES ($1, $2, $3)
		`, targetUserID, serverID, roleID)
		if err != nil {
			http.Error(w, `{"error":"failed to assign role"}`, http.StatusInternalServerError)
			return
		}
	}

	if err := tx.Commit(); err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// GetMemberRoles handles GET /servers/{serverID}/members/{userID}/roles.
func (h *RoleHandler) GetMemberRoles(w http.ResponseWriter, r *http.Request) {
	serverID := chi.URLParam(r, "serverID")
	targetUserID := chi.URLParam(r, "userID")

	rows, err := h.db.QueryContext(r.Context(), `
		SELECT r.id, r.server_id, r.name, r.color, r.permissions, r.position, r.is_default, r.created_at
		FROM roles r
		INNER JOIN member_roles mr ON mr.role_id = r.id
		WHERE mr.user_id = $1 AND mr.server_id = $2
		ORDER BY r.position DESC, r.name
	`, targetUserID, serverID)
	if err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	roles := make([]roleResponse, 0)
	for rows.Next() {
		role, err := scanRole(rows)
		if err != nil {
			http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
			return
		}
		roles = append(roles, role)
	}
	if err := rows.Err(); err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(roles)
}

// SeedDefaultRole inserts the @everyone, Admin, and Moderator roles for a newly created server.
// Call this after creating a server (e.g. from the server creation handler).
func SeedDefaultRole(ctx context.Context, db *sql.DB, serverID string) error {
	// @everyone
	_, err := db.ExecContext(ctx, `
		INSERT INTO roles (server_id, name, permissions, position, is_default, color)
		VALUES ($1, '@everyone', $2, 0, TRUE, NULL)
	`, serverID, permissions.DefaultEveryonePermissions)
	if err != nil {
		return err
	}

	// Admin
	adminPerms := permissions.ADMINISTRATOR
	_, err = db.ExecContext(ctx, `
		INSERT INTO roles (server_id, name, permissions, position, is_default, color)
		VALUES ($1, 'Admin', $2, 99, FALSE, '#e06c75')
	`, serverID, adminPerms)
	if err != nil {
		return err
	}

	// Moderator
	modPerms := permissions.VIEW_CHANNEL | permissions.SEND_MESSAGES | permissions.MANAGE_MESSAGES | permissions.KICK_MEMBERS | permissions.BAN_MEMBERS | permissions.MUTE_MEMBERS | permissions.CONNECT_VOICE | permissions.SPEAK_VOICE
	_, err = db.ExecContext(ctx, `
		INSERT INTO roles (server_id, name, permissions, position, is_default, color)
		VALUES ($1, 'Moderator', $2, 50, FALSE, '#61afef')
	`, serverID, modPerms)

	return err
}
