package handlers

import (
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"

	"scrum-solitare/src/state"
)

type RoomAPIHandler struct {
	manager *state.Manager
}

func NewRoomAPIHandler(manager *state.Manager) *RoomAPIHandler {
	return &RoomAPIHandler{manager: manager}
}

type createRoomRequest struct {
	RoomName         string   `json:"roomName"`
	CreatorUsername  string   `json:"creatorUsername"`
	MaxPeople        int      `json:"maxPeople"`
	Cards            []string `json:"cards"`
	AllowSpectators  bool     `json:"allowSpectators"`
	AnonymousVoting  bool     `json:"anonymousVoting"`
	AutoReset        bool     `json:"autoReset"`
	RevealMode       string   `json:"revealMode"`
	VotingTimeoutSec int      `json:"votingTimeoutSec"`
	Password         string   `json:"password"`
}

type joinRoomRequest struct {
	ParticipantID string `json:"participantId"`
	Username      string `json:"username"`
	Role          string `json:"role"`
	Password      string `json:"password"`
	AdminToken    string `json:"adminToken"`
}

type voteRequest struct {
	ParticipantID string `json:"participantId"`
	Card          string `json:"card"`
}

type adminActionRequest struct {
	ParticipantID string `json:"participantId"`
}

func (h *RoomAPIHandler) CreateRoom(c *gin.Context) {
	var req createRoomRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request payload"})
		return
	}

	result, err := h.manager.CreateRoom(state.CreateRoomInput{
		RoomName:         req.RoomName,
		CreatorUsername:  req.CreatorUsername,
		MaxPeople:        req.MaxPeople,
		Cards:            req.Cards,
		AllowSpectators:  req.AllowSpectators,
		AnonymousVoting:  req.AnonymousVoting,
		AutoReset:        req.AutoReset,
		RevealMode:       req.RevealMode,
		VotingTimeoutSec: req.VotingTimeoutSec,
		Password:         req.Password,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create room"})
		return
	}

	c.JSON(http.StatusCreated, result)
}

func (h *RoomAPIHandler) JoinRoom(c *gin.Context) {
	var req joinRoomRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request payload"})
		return
	}

	result, err := h.manager.JoinRoom(c.Param("roomID"), state.JoinRoomInput{
		ParticipantID: req.ParticipantID,
		Username:      req.Username,
		Role:          req.Role,
		Password:      req.Password,
		AdminToken:    req.AdminToken,
	})
	if err != nil {
		h.writeStateError(c, err)
		return
	}

	c.JSON(http.StatusOK, result)
}

func (h *RoomAPIHandler) StreamEvents(c *gin.Context) {
	roomID := c.Param("roomID")
	participantID := c.Query("participantId")
	if participantID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "participantId is required"})
		return
	}

	stream, initial, unsubscribe, err := h.manager.Subscribe(roomID, participantID)
	if err != nil {
		h.writeStateError(c, err)
		return
	}
	defer unsubscribe()

	c.Header("Content-Type", "text/event-stream")
	c.Header("Cache-Control", "no-cache")
	c.Header("Connection", "keep-alive")
	c.Header("X-Accel-Buffering", "no")

	flusher, ok := c.Writer.(http.Flusher)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "streaming unsupported"})
		return
	}

	sendEvent := func(event string, payload []byte) error {
		if _, err := fmt.Fprintf(c.Writer, "event: %s\n", event); err != nil {
			return err
		}
		if _, err := fmt.Fprintf(c.Writer, "data: %s\n\n", payload); err != nil {
			return err
		}
		flusher.Flush()
		return nil
	}

	if err := sendEvent("state", initial); err != nil {
		return
	}

	pingTicker := time.NewTicker(20 * time.Second)
	defer pingTicker.Stop()

	for {
		select {
		case <-c.Request.Context().Done():
			return
		case payload, ok := <-stream:
			if !ok {
				return
			}
			if err := sendEvent("state", payload); err != nil {
				return
			}
		case <-pingTicker.C:
			if _, err := c.Writer.Write([]byte(": ping\n\n")); err != nil {
				return
			}
			flusher.Flush()
		}
	}
}

func (h *RoomAPIHandler) CastVote(c *gin.Context) {
	var req voteRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request payload"})
		return
	}

	err := h.manager.CastVote(c.Param("roomID"), req.ParticipantID, req.Card)
	if err != nil {
		h.writeStateError(c, err)
		return
	}

	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (h *RoomAPIHandler) RevealVotes(c *gin.Context) {
	h.handleAdminAction(c, h.manager.RevealVotes)
}

func (h *RoomAPIHandler) ResetVotes(c *gin.Context) {
	h.handleAdminAction(c, h.manager.ResetVotes)
}

func (h *RoomAPIHandler) LeaveRoom(c *gin.Context) {
	var req adminActionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request payload"})
		return
	}

	if err := h.manager.LeaveRoom(c.Param("roomID"), req.ParticipantID); err != nil {
		h.writeStateError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (h *RoomAPIHandler) handleAdminAction(c *gin.Context, fn func(string, string) error) {
	var req adminActionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request payload"})
		return
	}

	if err := fn(c.Param("roomID"), req.ParticipantID); err != nil {
		h.writeStateError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (h *RoomAPIHandler) writeStateError(c *gin.Context, err error) {
	status := http.StatusInternalServerError
	message := "internal error"

	switch {
	case errors.Is(err, state.ErrRoomNotFound):
		status = http.StatusNotFound
		message = err.Error()
	case errors.Is(err, state.ErrParticipantNotFound):
		status = http.StatusNotFound
		message = err.Error()
	case errors.Is(err, state.ErrUnauthorized):
		status = http.StatusForbidden
		message = err.Error()
	case errors.Is(err, state.ErrRoomFull):
		status = http.StatusConflict
		message = err.Error()
	case errors.Is(err, state.ErrPasswordRequired):
		status = http.StatusUnauthorized
		message = err.Error()
	case errors.Is(err, state.ErrSpectatorsBlocked), errors.Is(err, state.ErrInvalidCard), errors.Is(err, state.ErrInvalidRole):
		status = http.StatusBadRequest
		message = err.Error()
	}

	c.JSON(status, gin.H{"error": message})
}
