package handlers

import (
	"context"
	"encoding/json"
	"strings"
	"sync"
	"time"

	"github.com/fpmb/server/internal/database"
	"github.com/fpmb/server/internal/models"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/websocket/v2"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type chatRoom struct {
	clients map[*websocket.Conn]*wsClient
	mu      sync.RWMutex
}

var chatRooms = struct {
	m  map[string]*chatRoom
	mu sync.RWMutex
}{m: make(map[string]*chatRoom)}

func getChatRoom(teamID string) *chatRoom {
	chatRooms.mu.Lock()
	defer chatRooms.mu.Unlock()
	if room, ok := chatRooms.m[teamID]; ok {
		return room
	}
	room := &chatRoom{clients: make(map[*websocket.Conn]*wsClient)}
	chatRooms.m[teamID] = room
	return room
}

func (r *chatRoom) broadcast(sender *websocket.Conn, msg []byte) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	for conn := range r.clients {
		if conn != sender {
			_ = conn.WriteMessage(websocket.TextMessage, msg)
		}
	}
}

func (r *chatRoom) broadcastAll(msg []byte) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	for conn := range r.clients {
		_ = conn.WriteMessage(websocket.TextMessage, msg)
	}
}

func (r *chatRoom) onlineUsers() []map[string]string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	seen := map[string]bool{}
	list := make([]map[string]string, 0)
	for _, c := range r.clients {
		if !seen[c.userID] {
			seen[c.userID] = true
			list = append(list, map[string]string{"user_id": c.userID, "name": c.name})
		}
	}
	return list
}

func ListChatMessages(c *fiber.Ctx) error {
	teamID := c.Params("teamId")
	teamOID, err := primitive.ObjectIDFromHex(teamID)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "Invalid team ID"})
	}

	limitStr := c.Query("limit", "50")
	limit := int64(50)
	if l, err := primitive.ParseDecimal128(limitStr); err == nil {
		if s := l.String(); s != "" {
			if n, err := parseIntFromString(s); err == nil && n > 0 && n <= 200 {
				limit = n
			}
		}
	}

	beforeStr := c.Query("before", "")
	filter := bson.M{"team_id": teamOID}
	if beforeStr != "" {
		if beforeID, err := primitive.ObjectIDFromHex(beforeStr); err == nil {
			filter["_id"] = bson.M{"$lt": beforeID}
		}
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	opts := options.Find().SetSort(bson.D{{Key: "_id", Value: -1}}).SetLimit(limit)
	cursor, err := database.GetCollection("chat_messages").Find(ctx, filter, opts)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch messages"})
	}
	defer cursor.Close(ctx)

	var messages []models.ChatMessage
	if err := cursor.All(ctx, &messages); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Failed to decode messages"})
	}
	if messages == nil {
		messages = []models.ChatMessage{}
	}

	for i, j := 0, len(messages)-1; i < j; i, j = i+1, j-1 {
		messages[i], messages[j] = messages[j], messages[i]
	}

	return c.JSON(messages)
}

func parseIntFromString(s string) (int64, error) {
	var n int64
	for _, ch := range s {
		if ch < '0' || ch > '9' {
			break
		}
		n = n*10 + int64(ch-'0')
	}
	return n, nil
}

func TeamChatWS(c *websocket.Conn) {
	teamID := c.Params("id")
	tokenStr := c.Query("token", "")
	userName := c.Query("name", "Anonymous")

	userID, _, ok := parseWSToken(tokenStr)
	if !ok {
		_ = c.WriteJSON(map[string]string{"type": "error", "message": "unauthorized"})
		_ = c.Close()
		return
	}

	room := getChatRoom(teamID)

	client := &wsClient{conn: c, userID: userID, name: userName}
	room.mu.Lock()
	room.clients[c] = client
	room.mu.Unlock()

	presenceMsg, _ := json.Marshal(map[string]interface{}{
		"type":  "presence",
		"users": room.onlineUsers(),
	})
	room.broadcastAll(presenceMsg)

	defer func() {
		room.mu.Lock()
		delete(room.clients, c)
		empty := len(room.clients) == 0
		room.mu.Unlock()

		leaveMsg, _ := json.Marshal(map[string]interface{}{
			"type":  "presence",
			"users": room.onlineUsers(),
		})
		room.broadcast(nil, leaveMsg)

		if empty {
			chatRooms.mu.Lock()
			delete(chatRooms.m, teamID)
			chatRooms.mu.Unlock()
		}
	}()

	for {
		_, msg, err := c.ReadMessage()
		if err != nil {
			if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
				// unexpected error
			}
			break
		}

		var incoming struct {
			Type      string `json:"type"`
			Content   string `json:"content"`
			MessageID string `json:"message_id,omitempty"`
			ReplyTo   string `json:"reply_to,omitempty"`
		}
		if json.Unmarshal(msg, &incoming) != nil {
			continue
		}

		teamOID, err := primitive.ObjectIDFromHex(teamID)
		if err != nil {
			continue
		}
		userOID, err := primitive.ObjectIDFromHex(userID)
		if err != nil {
			continue
		}

		if incoming.Type == "message" {
			content := strings.TrimSpace(incoming.Content)
			if content == "" || len(content) > 5000 {
				continue
			}

			chatMsg := models.ChatMessage{
				ID:        primitive.NewObjectID(),
				TeamID:    teamOID,
				UserID:    userOID,
				UserName:  userName,
				Content:   content,
				CreatedAt: time.Now(),
			}

			if incoming.ReplyTo != "" {
				if replyID, err := primitive.ObjectIDFromHex(incoming.ReplyTo); err == nil {
					chatMsg.ReplyTo = &replyID
				}
			}

			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			_, _ = database.GetCollection("chat_messages").InsertOne(ctx, chatMsg)
			cancel()

			outMsg, _ := json.Marshal(map[string]interface{}{
				"type":    "message",
				"message": chatMsg,
			})
			room.broadcastAll(outMsg)
		} else if incoming.Type == "edit" {
			content := strings.TrimSpace(incoming.Content)
			if content == "" || len(content) > 5000 || incoming.MessageID == "" {
				continue
			}
			msgID, err := primitive.ObjectIDFromHex(incoming.MessageID)
			if err != nil {
				continue
			}

			now := time.Now()
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			res, err := database.GetCollection("chat_messages").UpdateOne(ctx,
				bson.M{"_id": msgID, "user_id": userOID, "team_id": teamOID, "deleted": bson.M{"$ne": true}},
				bson.M{"$set": bson.M{"content": content, "edited_at": now}},
			)
			cancel()

			if err == nil && res.ModifiedCount > 0 {
				outMsg, _ := json.Marshal(map[string]interface{}{
					"type":       "edit",
					"message_id": msgID.Hex(),
					"content":    content,
					"edited_at":  now,
				})
				room.broadcastAll(outMsg)
			}
		} else if incoming.Type == "delete" {
			if incoming.MessageID == "" {
				continue
			}
			msgID, err := primitive.ObjectIDFromHex(incoming.MessageID)
			if err != nil {
				continue
			}

			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			res, err := database.GetCollection("chat_messages").UpdateOne(ctx,
				bson.M{"_id": msgID, "user_id": userOID, "team_id": teamOID},
				bson.M{"$set": bson.M{"deleted": true, "content": ""}},
			)
			cancel()

			if err == nil && res.ModifiedCount > 0 {
				outMsg, _ := json.Marshal(map[string]interface{}{
					"type":       "delete",
					"message_id": msgID.Hex(),
				})
				room.broadcastAll(outMsg)
			}
		}

		if incoming.Type == "typing" {
			typingMsg, _ := json.Marshal(map[string]interface{}{
				"type":    "typing",
				"user_id": userID,
				"name":    userName,
			})
			room.broadcast(c, typingMsg)
		}
	}
}
