package realtime

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

const closeWriteWait = 10 * time.Second

type Client struct {
	hub    *Hub
	userID uint64
	send   chan []byte
	subs   map[string]struct{}
	mu     sync.RWMutex

	connMu sync.Mutex
	conn   *websocket.Conn
}

func (c *Client) subscribed(ch string) bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	_, ok := c.subs[ch]
	return ok
}

func (c *Client) addSub(ch string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.subs == nil {
		c.subs = make(map[string]struct{})
	}
	c.subs[ch] = struct{}{}
}

// BindConn attaches the upgraded WebSocket (for forced disconnect on logout).
func (c *Client) BindConn(conn *websocket.Conn) {
	c.connMu.Lock()
	defer c.connMu.Unlock()
	c.conn = conn
}

// CloseConn closes the WebSocket from the server side (e.g. session revoked).
func (c *Client) CloseConn() {
	c.connMu.Lock()
	defer c.connMu.Unlock()
	if c.conn == nil {
		return
	}
	_ = c.conn.WriteControl(
		websocket.CloseMessage,
		websocket.FormatCloseMessage(websocket.CloseGoingAway, "session ended"),
		time.Now().Add(closeWriteWait),
	)
	_ = c.conn.Close()
	c.conn = nil
}

type Hub struct {
	clients    map[*Client]bool
	register   chan *Client
	unregister chan *Client
	mu         sync.Mutex

	ticketMu sync.Mutex
	tickets  map[string]ticketData
}

type ticketData struct {
	userID uint64
	exp    time.Time
}

func randomTicket() (string, error) {
	var b [24]byte
	if _, err := rand.Read(b[:]); err != nil {
		return "", err
	}
	return hex.EncodeToString(b[:]), nil
}

func NewHub() *Hub {
	return &Hub{
		clients:    make(map[*Client]bool),
		register:   make(chan *Client),
		unregister: make(chan *Client),
		tickets:    make(map[string]ticketData),
	}
}

func (h *Hub) Run() {
	for {
		select {
		case c := <-h.register:
			h.mu.Lock()
			h.clients[c] = true
			h.mu.Unlock()
		case c := <-h.unregister:
			h.mu.Lock()
			if _, ok := h.clients[c]; ok {
				delete(h.clients, c)
				close(c.send)
			}
			h.mu.Unlock()
		}
	}
}

func (h *Hub) IssueTicket(userID uint64, ttl time.Duration) (string, error) {
	raw, err := randomTicket()
	if err != nil {
		return "", err
	}
	h.ticketMu.Lock()
	h.tickets[raw] = ticketData{userID: userID, exp: time.Now().Add(ttl)}
	for k, v := range h.tickets {
		if time.Now().After(v.exp) {
			delete(h.tickets, k)
		}
	}
	h.ticketMu.Unlock()
	return raw, nil
}

func (h *Hub) ConsumeTicket(t string) (uint64, bool) {
	h.ticketMu.Lock()
	defer h.ticketMu.Unlock()
	td, ok := h.tickets[t]
	if !ok || time.Now().After(td.exp) {
		return 0, false
	}
	delete(h.tickets, t)
	return td.userID, true
}

func (h *Hub) Register(c *Client) {
	h.register <- c
}

func (h *Hub) Unregister(c *Client) {
	h.unregister <- c
}

// DisconnectUser closes all WebSocket connections for the given user (logout / revoke).
func (h *Hub) DisconnectUser(userID uint64) {
	h.mu.Lock()
	list := make([]*Client, 0)
	for c := range h.clients {
		if c.userID == userID {
			list = append(list, c)
		}
	}
	h.mu.Unlock()
	for _, c := range list {
		c.CloseConn()
	}
}

func (h *Hub) PublishToChannel(channel string, eventType string, payload interface{}) {
	b, err := json.Marshal(map[string]interface{}{
		"type":    eventType,
		"channel": channel,
		"payload": payload,
	})
	if err != nil {
		return
	}
	h.mu.Lock()
	clients := make([]*Client, 0, len(h.clients))
	for c := range h.clients {
		clients = append(clients, c)
	}
	h.mu.Unlock()
	for _, c := range clients {
		if !c.subscribed(channel) {
			continue
		}
		select {
		case c.send <- b:
		default:
		}
	}
}

func (h *Hub) BroadcastEvent(eventType string, payload interface{}) {
	b, err := json.Marshal(map[string]interface{}{
		"type":    eventType,
		"payload": payload,
	})
	if err != nil {
		return
	}
	h.mu.Lock()
	clients := make([]*Client, 0, len(h.clients))
	for c := range h.clients {
		clients = append(clients, c)
	}
	h.mu.Unlock()
	for _, c := range clients {
		select {
		case c.send <- b:
		default:
		}
	}
}
