package realtime

import (
	"encoding/json"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/gorilla/websocket"
)

const (
	writeWait      = 10 * time.Second
	pongWait       = 60 * time.Second
	pingPeriod     = (pongWait * 9) / 10
	maxMessageSize = 1 << 16
)

func NewClient(h *Hub, userID uint64) *Client {
	return &Client{
		hub:    h,
		userID: userID,
		send:   make(chan []byte, 64),
	}
}

func (c *Client) readPump(conn *websocket.Conn, isMember func(teamID uint64) bool) {
	defer func() {
		c.hub.Unregister(c)
		_ = conn.Close()
	}()
	_ = conn.SetReadDeadline(time.Now().Add(pongWait))
	conn.SetPongHandler(func(string) error {
		_ = conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})
	conn.SetReadLimit(maxMessageSize)
	for {
		_, message, err := conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("websocket read: %v", err)
			}
			break
		}
		var m struct {
			Type    string `json:"type"`
			Channel string `json:"channel"`
		}
		if err := json.Unmarshal(message, &m); err != nil {
			continue
		}
		switch m.Type {
		case "ping":
			_ = conn.SetWriteDeadline(time.Now().Add(writeWait))
			_ = conn.WriteJSON(map[string]string{"type": "pong"})
		case "subscribe":
			if !strings.HasPrefix(m.Channel, "team:") {
				continue
			}
			idStr := strings.TrimPrefix(m.Channel, "team:")
			tid, err := strconv.ParseUint(idStr, 10, 64)
			if err != nil || !isMember(tid) {
				continue
			}
			c.addSub(m.Channel)
		}
	}
}

func (c *Client) writePump(conn *websocket.Conn) {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		_ = conn.Close()
	}()
	for {
		select {
		case message, ok := <-c.send:
			_ = conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				_ = conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
				return
			}
		case <-ticker.C:
			_ = conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

func RunPeer(h *Hub, userID uint64, conn *websocket.Conn, isMember func(teamID uint64) bool) {
	c := NewClient(h, userID)
	c.BindConn(conn)
	c.hub.Register(c)
	go c.writePump(conn)
	c.readPump(conn, isMember)
}
