package message

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

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

type PollHandler struct {
	db      *sql.DB
	hub     *gateway.Hub
	checker *permissions.Checker
}

func NewPollHandler(db *sql.DB, hub *gateway.Hub, checker *permissions.Checker) *PollHandler {
	return &PollHandler{db: db, hub: hub, checker: checker}
}

func (ph *PollHandler) RegisterRoutes(r chi.Router) {
	r.Post("/", ph.Create)
	r.Get("/{pollID}", ph.Get)
	r.Post("/{pollID}/vote", ph.Vote)
}

type createPollRequest struct {
	ChannelID string   `json:"channel_id"`
	Question  string   `json:"question"`
	Options   []string `json:"options"`
}

type pollOptionResponse struct {
	ID       string   `json:"id"`
	Text     string   `json:"text"`
	Position int      `json:"position"`
	Votes    int      `json:"votes"`
	Voters   []string `json:"voters"`
}

type pollResponse struct {
	ID        string               `json:"id"`
	MessageID string               `json:"message_id"`
	Question  string               `json:"question"`
	Options   []pollOptionResponse `json:"options"`
	CreatedAt string               `json:"created_at"`
}

func (ph *PollHandler) Create(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return
	}

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

	if req.Question == "" || len(req.Options) < 2 || len(req.Options) > 10 {
		http.Error(w, `{"error":"question required, 2-10 options"}`, http.StatusBadRequest)
		return
	}

	var serverID string
	err := ph.db.QueryRowContext(r.Context(), `
		SELECT server_id FROM channels WHERE id = $1
	`, req.ChannelID).Scan(&serverID)
	if err != nil {
		http.Error(w, `{"error":"channel not found"}`, http.StatusNotFound)
		return
	}

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

	var msgID string
	err = tx.QueryRowContext(r.Context(), `
		INSERT INTO messages (channel_id, author_id, content)
		VALUES ($1, $2, $3)
		RETURNING id
	`, req.ChannelID, userID, req.Question).Scan(&msgID)
	if err != nil {
		http.Error(w, `{"error":"failed to create message"}`, http.StatusInternalServerError)
		return
	}

	var pollID string
	err = tx.QueryRowContext(r.Context(), `
		INSERT INTO polls (message_id, question)
		VALUES ($1, $2)
		RETURNING id
	`, msgID, req.Question).Scan(&pollID)
	if err != nil {
		http.Error(w, `{"error":"failed to create poll"}`, http.StatusInternalServerError)
		return
	}

	options := make([]pollOptionResponse, len(req.Options))
	for i, text := range req.Options {
		var optID string
		err = tx.QueryRowContext(r.Context(), `
			INSERT INTO poll_options (poll_id, text, position)
			VALUES ($1, $2, $3)
			RETURNING id
		`, pollID, text, i).Scan(&optID)
		if err != nil {
			http.Error(w, `{"error":"failed to create option"}`, http.StatusInternalServerError)
			return
		}
		options[i] = pollOptionResponse{
			ID:       optID,
			Text:     text,
			Position: i,
			Votes:    0,
			Voters:   []string{},
		}
	}

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

	var authorName string
	var displayName sql.NullString
	_ = ph.db.QueryRowContext(r.Context(), `
		SELECT username, display_name FROM users WHERE id = $1
	`, userID).Scan(&authorName, &displayName)

	var createdAt string
	_ = ph.db.QueryRowContext(r.Context(), `SELECT created_at::text FROM messages WHERE id = $1`, msgID).Scan(&createdAt)

	poll := pollResponse{
		ID:        pollID,
		MessageID: msgID,
		Question:  req.Question,
		Options:   options,
		CreatedAt: createdAt,
	}

	broadcastMsg := map[string]interface{}{
		"id":                  msgID,
		"channel_id":          req.ChannelID,
		"author_id":           userID,
		"author_username":     authorName,
		"author_display_name": displayName.String,
		"content":             req.Question,
		"pinned":              false,
		"created_at":          createdAt,
		"reactions":           []interface{}{},
		"embeds":              []interface{}{},
		"poll":                poll,
	}
	ph.hub.BroadcastToServer(serverID, gateway.Event{
		Type: gateway.EventMessageCreate,
		Data: broadcastMsg,
	})

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

func (ph *PollHandler) Get(w http.ResponseWriter, r *http.Request) {
	pollID := chi.URLParam(r, "pollID")

	poll, err := ph.fetchPoll(r.Context(), pollID)
	if err != nil {
		http.Error(w, `{"error":"poll not found"}`, http.StatusNotFound)
		return
	}

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

type voteRequest struct {
	OptionID string `json:"option_id"`
}

func (ph *PollHandler) Vote(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return
	}

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

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

	var optPollID string
	err := ph.db.QueryRowContext(r.Context(), `
		SELECT poll_id FROM poll_options WHERE id = $1
	`, req.OptionID).Scan(&optPollID)
	if err != nil || optPollID != pollID {
		http.Error(w, `{"error":"invalid option"}`, http.StatusBadRequest)
		return
	}

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

	_, err = tx.ExecContext(r.Context(), `
		DELETE FROM poll_votes
		WHERE user_id = $1
		AND option_id IN (SELECT id FROM poll_options WHERE poll_id = $2)
	`, userID, pollID)
	if err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}

	_, err = tx.ExecContext(r.Context(), `
		INSERT INTO poll_votes (option_id, user_id)
		VALUES ($1, $2)
	`, req.OptionID, userID)
	if err != nil {
		http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError)
		return
	}

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

	// Get server for broadcast
	var msgID, channelID, serverID string
	_ = ph.db.QueryRowContext(r.Context(), `
		SELECT p.message_id, m.channel_id, c.server_id
		FROM polls p
		JOIN messages m ON m.id = p.message_id
		JOIN channels c ON c.id = m.channel_id
		WHERE p.id = $1
	`, pollID).Scan(&msgID, &channelID, &serverID)

	pollResp, _ := ph.fetchPoll(r.Context(), pollID)
	if pollResp != nil {
		ph.hub.BroadcastToServer(serverID, gateway.Event{
			Type: "POLL_UPDATE",
			Data: map[string]interface{}{
				"poll":       pollResp,
				"message_id": msgID,
				"channel_id": channelID,
			},
		})
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

func (ph *PollHandler) fetchPoll(ctx context.Context, pollID string) (*pollResponse, error) {
	var poll pollResponse
	var createdAt sql.NullString
	err := ph.db.QueryRowContext(ctx, `
		SELECT id, message_id, question, created_at::text FROM polls WHERE id = $1
	`, pollID).Scan(&poll.ID, &poll.MessageID, &poll.Question, &createdAt)
	if err != nil {
		return nil, err
	}
	if createdAt.Valid {
		poll.CreatedAt = createdAt.String
	}

	rows, err := ph.db.QueryContext(ctx, `
		SELECT po.id, po.text, po.position, COUNT(pv.id) as votes
		FROM poll_options po
		LEFT JOIN poll_votes pv ON pv.option_id = po.id
		WHERE po.poll_id = $1
		GROUP BY po.id, po.text, po.position
		ORDER BY po.position
	`, pollID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	poll.Options = make([]pollOptionResponse, 0)
	for rows.Next() {
		var opt pollOptionResponse
		if err := rows.Scan(&opt.ID, &opt.Text, &opt.Position, &opt.Votes); err != nil {
			continue
		}
		opt.Voters = []string{}
		poll.Options = append(poll.Options, opt)
	}

	for i, opt := range poll.Options {
		voterRows, err := ph.db.QueryContext(ctx, `SELECT user_id FROM poll_votes WHERE option_id = $1`, opt.ID)
		if err != nil {
			continue
		}
		for voterRows.Next() {
			var voterID string
			if voterRows.Scan(&voterID) == nil {
				poll.Options[i].Voters = append(poll.Options[i].Voters, voterID)
			}
		}
		voterRows.Close()
	}

	return &poll, nil
}
