package websocket

import (
	"encoding/json"
	"log"
	"strconv"
	"sync"

	"github.com/gofiber/websocket/v2"
	"gorm.io/gorm"
	internalDb "server/internal/db"
)

type SocketMsg struct {
	Event string         `json:"event"`
	Data  map[string]any `json:"data"`
}

type SocketClient struct {
	Conn      *websocket.Conn
	StudentID int64
	ClassID   int64
	StaffID   uint
	Role      string // "student" | "teacher"
	Addr      string
}

type WsHub struct {
	mu              sync.RWMutex
	students        map[int64]*SocketClient
	teachers        map[string]*SocketClient
	teachersByStaff map[uint][]string // staffId -> teacher connection addresses
	subscribers     map[int64][]string // studentId -> list of teacher connection addresses
}

var Hub = &WsHub{
	students:        make(map[int64]*SocketClient),
	teachers:        make(map[string]*SocketClient),
	teachersByStaff: make(map[uint][]string),
	subscribers:     make(map[int64][]string),
}

func (h *WsHub) IsStudentOnline(studentRkID int64) bool {
	h.mu.RLock()
	defer h.mu.RUnlock()
	_, ok := h.students[studentRkID]
	return ok
}

func (h *WsHub) PushExamToStudent(studentRkID int64, data map[string]any) {
	h.mu.RLock()
	client, ok := h.students[studentRkID]
	h.mu.RUnlock()
	if !ok || client == nil {
		return
	}
	_ = client.Conn.WriteJSON(SocketMsg{Event: "exam:paper-sent", Data: data})
}

func (h *WsHub) PushChatToStudent(studentRkID int64, data map[string]any) {
	h.mu.RLock()
	client, ok := h.students[studentRkID]
	h.mu.RUnlock()
	if !ok || client == nil {
		return
	}
	_ = client.Conn.WriteJSON(SocketMsg{Event: "chat:message", Data: data})
}

func (h *WsHub) PushChatToStaff(staffID uint, data map[string]any) {
	if data == nil {
		data = map[string]any{}
	}
	data["targetStaffId"] = staffID
	msg := SocketMsg{Event: "chat:message", Data: data}
	h.mu.RLock()
	defer h.mu.RUnlock()
	for _, t := range h.teachers {
		if t != nil && t.Role == "teacher" {
			_ = t.Conn.WriteJSON(msg)
		}
	}
}

func (h *WsHub) GetOnlineStudentIDs(classID int64) []int64 {
	h.mu.RLock()
	defer h.mu.RUnlock()

	var ids []int64
	for _, client := range h.students {
		if client.ClassID == classID {
			ids = append(ids, client.StudentID)
		}
	}
	if ids == nil {
		return []int64{}
	}
	return ids
}

func (h *WsHub) Register(c *SocketClient) {
	h.mu.Lock()
	defer h.mu.Unlock()

	c.Addr = c.Conn.RemoteAddr().String()
	if c.Role == "student" {
		h.students[c.StudentID] = c
		log.Printf("[WS] Student %d registered (Address: %s, Class: %d)", c.StudentID, c.Addr, c.ClassID)
	} else if c.Role == "teacher" {
		h.teachers[c.Addr] = c
		if c.StaffID > 0 {
			h.teachersByStaff[c.StaffID] = append(h.teachersByStaff[c.StaffID], c.Addr)
		}
		log.Printf("[WS] Teacher registered (Address: %s, Staff: %d)", c.Addr, c.StaffID)
	}
}

func (h *WsHub) Unregister(c *SocketClient) {
	h.mu.Lock()
	defer h.mu.Unlock()

	if c.Role == "student" {
		delete(h.students, c.StudentID)
		log.Printf("[WS] Student %d disconnected", c.StudentID)

		// Báo cho các giáo viên đang xem là stream của học sinh đã dừng
		if teachers, exists := h.subscribers[c.StudentID]; exists {
			for _, tAddr := range teachers {
				if t, found := h.teachers[tAddr]; found {
					_ = t.Conn.WriteJSON(SocketMsg{
						Event: "teacher:stream-stopped",
						Data:  map[string]any{"studentId": c.StudentID},
					})
				}
			}
			delete(h.subscribers, c.StudentID)
		}
	} else if c.Role == "teacher" {
		delete(h.teachers, c.Addr)
		if c.StaffID > 0 {
			list := h.teachersByStaff[c.StaffID]
			next := list[:0]
			for _, addr := range list {
				if addr != c.Addr {
					next = append(next, addr)
				}
			}
			if len(next) == 0 {
				delete(h.teachersByStaff, c.StaffID)
			} else {
				h.teachersByStaff[c.StaffID] = next
			}
		}
		log.Printf("[WS] Teacher %s disconnected", c.Addr)

		// Dọn dẹp subscriptions của giáo viên này
		for sID, teachersList := range h.subscribers {
			newList := []string{}
			for _, addr := range teachersList {
				if addr != c.Addr {
					newList = append(newList, addr)
				}
			}
			if len(newList) == 0 {
				delete(h.subscribers, sID)
				// Nếu không còn ai xem học sinh này, gửi lệnh tắt camera/screen cho client học sinh
				if student, exists := h.students[sID]; exists {
					_ = student.Conn.WriteJSON(SocketMsg{Event: "stop_screenshot_stream"})
					_ = student.Conn.WriteJSON(SocketMsg{Event: "stop_webcam_stream"})
				}
			} else {
				h.subscribers[sID] = newList
			}
		}
	}
}

// Teacher bắt đầu xem stream của Student
func (h *WsHub) Subscribe(teacherAddr string, studentID int64) {
	h.mu.Lock()
	defer h.mu.Unlock()

	// Thêm giáo viên vào list người xem của học sinh
	teachersList := h.subscribers[studentID]
	alreadySubscribed := false
	for _, addr := range teachersList {
		if addr == teacherAddr {
			alreadySubscribed = true
			break
		}
	}
	if !alreadySubscribed {
		h.subscribers[studentID] = append(teachersList, teacherAddr)
		log.Printf("[WS] Teacher %s subscribed to student %d stream", teacherAddr, studentID)
	}

	// Phát lệnh cho máy học sinh bật stream (nếu học sinh đang online)
	if student, exists := h.students[studentID]; exists {
		_ = student.Conn.WriteJSON(SocketMsg{Event: "start_screenshot_stream"})
		_ = student.Conn.WriteJSON(SocketMsg{Event: "start_webcam_stream"})
	}
}

// Teacher dừng xem stream của Student
func (h *WsHub) Unsubscribe(teacherAddr string, studentID int64) {
	h.mu.Lock()
	defer h.mu.Unlock()

	teachersList, exists := h.subscribers[studentID]
	if !exists {
		return
	}

	newList := []string{}
	for _, addr := range teachersList {
		if addr != teacherAddr {
			newList = append(newList, addr)
		}
	}

	if len(newList) == 0 {
		delete(h.subscribers, studentID)
		log.Printf("[WS] Student %d has no more proctor subscribers. Stopping streams.", studentID)

		// Báo học sinh tắt camera & screen stream để tiết kiệm mạng và CPU
		if student, exists := h.students[studentID]; exists {
			_ = student.Conn.WriteJSON(SocketMsg{Event: "stop_screenshot_stream"})
			_ = student.Conn.WriteJSON(SocketMsg{Event: "stop_webcam_stream"})
		}
	} else {
		h.subscribers[studentID] = newList
	}
}

// Chuyển tiếp frame ảnh từ Student đến các Teacher đã subscribe
func (h *WsHub) RelayFrame(studentID int64, event string, data map[string]any) {
	h.mu.RLock()
	defer h.mu.RUnlock()

	teachersList, exists := h.subscribers[studentID]
	if !exists || len(teachersList) == 0 {
		return
	}

	relayEvent := "teacher:screenshot-stream-frame"
	if event == "webcam_stream_frame" {
		relayEvent = "teacher:webcam-stream-frame"
	}

	msg := SocketMsg{
		Event: relayEvent,
		Data: map[string]any{
			"studentId":   studentID,
			"imageBuffer": data["imageBuffer"],
		},
	}

	for _, addr := range teachersList {
		if t, found := h.teachers[addr]; found {
			_ = t.Conn.WriteJSON(msg)
		}
	}
}

// WebSocket handler cho Fiber route
func WebSocketHandler(db *gorm.DB) func(*websocket.Conn) {
	return func(c *websocket.Conn) {
		role := c.Query("role", "student")
		studentIDStr := c.Query("studentId", "0")
		classIDStr := c.Query("classId", "0")
		staffIDStr := c.Query("staffId", "0")

		studentID, _ := strconv.ParseInt(studentIDStr, 10, 64)
		classID, _ := strconv.ParseInt(classIDStr, 10, 64)
		staffID64, _ := strconv.ParseUint(staffIDStr, 10, 64)

		if role == "student" && db != nil {
			resolvedClassID := internalDb.FindActiveClassForStudent(db, studentID)
			if resolvedClassID > 0 {
				classID = resolvedClassID
			}
		}

		client := &SocketClient{
			Conn:      c,
			StudentID: studentID,
			ClassID:   classID,
			StaffID:   uint(staffID64),
			Role:      role,
		}

		Hub.Register(client)
		defer func() {
			Hub.Unregister(client)
			c.Close()
		}()

		for {
			_, msgBytes, err := c.ReadMessage()
			if err != nil {
				break
			}

			var msg SocketMsg
			if err := json.Unmarshal(msgBytes, &msg); err != nil {
				continue
			}

			// Xử lý các sự kiện
			switch msg.Event {
			case "screenshot_stream_frame", "webcam_stream_frame":
				// Nhận frame từ học sinh, chuyển tiếp về các thầy cô
				Hub.RelayFrame(client.StudentID, msg.Event, msg.Data)

			case "teacher:subscribe":
				// Giáo viên đăng ký xem học sinh cụ thể
				if client.Role == "teacher" {
					if sIDVal, ok := msg.Data["studentId"]; ok {
						var sID int64
						switch v := sIDVal.(type) {
						case float64:
							sID = int64(v)
						case string:
							sID, _ = strconv.ParseInt(v, 10, 64)
						}
						if sID > 0 {
							Hub.Subscribe(client.Addr, sID)
						}
					}
				}

			case "teacher:unsubscribe":
				// Giáo viên hủy đăng ký
				if client.Role == "teacher" {
					if sIDVal, ok := msg.Data["studentId"]; ok {
						var sID int64
						switch v := sIDVal.(type) {
						case float64:
							sID = int64(v)
						case string:
							sID, _ = strconv.ParseInt(v, 10, 64)
						}
						if sID > 0 {
							Hub.Unsubscribe(client.Addr, sID)
						}
					}
				}
			}
		}
	}
}
