package channel

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

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

type createThreadRequest struct {
	Name      string   `json:"name"`
	MessageID *string  `json:"message_id,omitempty"`
	TagIDs    []string `json:"tag_ids,omitempty"`
}

type threadResponse struct {
	ID                  string  `json:"id"`
	ServerID            string  `json:"server_id"`
	ParentChannelID     string  `json:"parent_channel_id"`
	Name                string  `json:"name"`
	Type                string  `json:"type"`
	Category            *string `json:"category"`
	Position            int     `json:"position"`
	ArchivedAt          *string `json:"archived_at"`
	AutoArchiveDuration int     `json:"auto_archive_duration"`
	MessageCount        int     `json:"message_count"`
	LastMessageAt       *string `json:"last_message_at"`
	CreatedAt           string  `json:"created_at"`
}

func (h *Handler) registerThreadRoutes(r chi.Router) {
	r.Post("/{channelID}/threads", h.CreateThread)
	r.Get("/{channelID}/threads", h.ListThreads)
	r.Patch("/threads/{threadID}", h.UpdateThread)
}

func scanThread(row interface{ Scan(dest ...any) error }) (threadResponse, error) {
	var t threadResponse
	var archivedAt, lastMsgAt, category sql.NullString
	err := row.Scan(&t.ID, &t.ServerID, &t.ParentChannelID, &t.Name, &t.Type, &category, &t.Position, &archivedAt, &t.AutoArchiveDuration, &t.MessageCount, &lastMsgAt, &t.CreatedAt)
	if err != nil {
		return t, err
	}
	if archivedAt.Valid {
		t.ArchivedAt = &archivedAt.String
	}
	if lastMsgAt.Valid {
		t.LastMessageAt = &lastMsgAt.String
	}
	if category.Valid {
		t.Category = &category.String
	}
	return t, nil
}

func (h *Handler) CreateThread(w http.ResponseWriter, r *http.Request) {
	parentID := chi.URLParam(r, "channelID")
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return
	}
	serverID, err := h.serverIDForChannel(r.Context(), parentID)
	if err != nil {
		http.Error(w, `{"error":"channel not found"}`, http.StatusNotFound)
		return
	}
	member, err := h.isMember(r.Context(), userID, serverID)
	if err != nil || !member {
		http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
		return
	}
	allowed, err := h.checker.CheckPermission(r.Context(), serverID, userID, permissions.SEND_MESSAGES)
	if err != nil || !allowed {
		http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
		return
	}

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

	// ponytail: reuse channels table with parent_channel_id set. Messages in the thread channel are normal messages.
	thread, err := scanThread(h.db.QueryRowContext(r.Context(), `
		INSERT INTO channels (server_id, name, type, category, position, parent_channel_id, auto_archive_duration)
		VALUES ($1, $2, 'thread', 'threads', 0, $3, 1440)
		RETURNING id, server_id, parent_channel_id, name, type, category, position, archived_at, auto_archive_duration, message_count, last_message_at, created_at
	`, serverID, req.Name, parentID))
	if err != nil {
		http.Error(w, `{"error":"failed to create thread"}`, http.StatusInternalServerError)
		return
	}

	// Attach optional tags for forum posts.
	if len(req.TagIDs) > 0 {
		// ponytail: ignore tag errors; best-effort assignment
		for _, tagID := range req.TagIDs {
			_, _ = h.db.ExecContext(r.Context(), `
				INSERT INTO channel_tags (channel_id, tag_id) VALUES ($1, $2) ON CONFLICT DO NOTHING
			`, thread.ID, tagID)
		}
	}

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

func (h *Handler) ListThreads(w http.ResponseWriter, r *http.Request) {
	parentID := chi.URLParam(r, "channelID")
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return
	}
	serverID, err := h.serverIDForChannel(r.Context(), parentID)
	if err != nil {
		http.Error(w, `{"error":"channel not found"}`, http.StatusNotFound)
		return
	}
	member, err := h.isMember(r.Context(), userID, serverID)
	if err != nil || !member {
		http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
		return
	}

	rows, err := h.db.QueryContext(r.Context(), `
		SELECT id, server_id, parent_channel_id, name, type, category, position, archived_at, auto_archive_duration, message_count, last_message_at, created_at
		FROM channels
		WHERE parent_channel_id = $1 AND server_id = $2 AND type = 'thread' AND archived_at IS NULL
		ORDER BY last_message_at DESC NULLS LAST, created_at DESC
	`, parentID, serverID)
	if err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	threads := make([]threadResponse, 0)
	for rows.Next() {
		t, err := scanThread(rows)
		if err != nil {
			continue
		}
		threads = append(threads, t)
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(threads)
}

type updateThreadRequest struct {
	Archived *bool `json:"archived"`
}

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

	var serverID string
	err := h.db.QueryRowContext(r.Context(), `SELECT server_id FROM channels WHERE id = $1 AND type = 'thread'`, threadID).Scan(&serverID)
	if err != nil {
		http.Error(w, `{"error":"thread not found"}`, http.StatusNotFound)
		return
	}

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

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

	var archivedAt interface{}
	if req.Archived != nil && *req.Archived {
		archivedAt = "NOW()"
	} else {
		archivedAt = nil
	}

	thread, err := scanThread(h.db.QueryRowContext(r.Context(), `
		UPDATE channels SET archived_at = COALESCE($1, archived_at) WHERE id = $2
		RETURNING id, server_id, parent_channel_id, name, type, category, position, archived_at, auto_archive_duration, message_count, last_message_at, created_at
	`, archivedAt, threadID))
	if err != nil {
		http.Error(w, `{"error":"failed to update thread"}`, http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(thread)
}
