package handlers

import (
	"strconv"
	"strings"
	"time"

	internalDb "server/internal/db"
	"server/internal/models"
	"server/internal/util"
	internalWs "server/internal/websocket"

	"github.com/gofiber/fiber/v2"
	"gorm.io/gorm"
)

type classWithScheduleItem struct {
	RkID           int64                  `json:"rkId"`
	Name           string                 `json:"name"`
	ClassCode      string                 `json:"classCode"`
	Type           string                 `json:"type"`
	StudentCount   int                    `json:"studentCount"`
	SpecializeName string                 `json:"specializeName"`
	SystemName     string                 `json:"systemName"`
	Schedules      []models.ClassSchedule `json:"schedules"`
	AllowedApps    string                 `json:"allowedApps"`
}

// GET /api/classes/schedules - Lấy các lớp có lịch học trong tuần
func ListActiveSchedulesHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		// Tìm các classRkId có lịch học
		var activeClassRkIDs []int64
		if err := db.Model(&models.ClassSchedule{}).Distinct("class_rk_id").Pluck("class_rk_id", &activeClassRkIDs).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		if len(activeClassRkIDs) == 0 {
			return c.JSON(fiber.Map{"data": []any{}})
		}

		// Lấy thông tin lớp học
		var classes []models.Class
		if err := db.Where("rk_id IN ?", activeClassRkIDs).Find(&classes).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		// Lấy toàn bộ lịch học của các lớp này
		var schedules []models.ClassSchedule
		if err := db.Where("class_rk_id IN ?", activeClassRkIDs).Order("day_of_week, start_time").Find(&schedules).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		// Lấy cấu hình allowed apps của các lớp này
		var allowedApps []models.ClassAllowedApp
		if err := db.Where("class_rk_id IN ?", activeClassRkIDs).Find(&allowedApps).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		// Map dữ liệu
		schedulesMap := map[int64][]models.ClassSchedule{}
		for _, s := range schedules {
			schedulesMap[s.ClassRkID] = append(schedulesMap[s.ClassRkID], s)
		}

		allowedAppsMap := map[int64]string{}
		for _, a := range allowedApps {
			allowedAppsMap[a.ClassRkID] = a.Keywords
		}

		out := make([]classWithScheduleItem, 0, len(classes))
		for _, cl := range classes {
			sList := schedulesMap[cl.RkID]
			if sList == nil {
				sList = []models.ClassSchedule{}
			}
			specName := ""
			if cl.SpecializeName != nil {
				specName = *cl.SpecializeName
			}
			sysName := ""
			if cl.SystemName != nil {
				sysName = *cl.SystemName
			}

			out = append(out, classWithScheduleItem{
				RkID:           cl.RkID,
				Name:           cl.Name,
				ClassCode:      cl.ClassCode,
				Type:           cl.Type,
				StudentCount:   cl.StudentCount,
				SpecializeName: specName,
				SystemName:     sysName,
				Schedules:      sList,
				AllowedApps:    allowedAppsMap[cl.RkID],
			})
		}

		return c.JSON(fiber.Map{"data": out})
	}
}

// GET /api/classes/:rkId/schedule - Lấy lịch học của lớp
func GetClassScheduleHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		rkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
		}

		var schedules []models.ClassSchedule
		if err := db.Where("class_rk_id = ?", rkID).Order("day_of_week, period, start_time").Find(&schedules).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		return c.JSON(fiber.Map{"data": schedules})
	}
}

// POST /api/classes/:rkId/schedule - Lưu lịch học của lớp
func SaveClassScheduleHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		rkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
		}

		type reqSchedule struct {
			DayOfWeek  int    `json:"dayOfWeek"`
			Period     int    `json:"period"`
			StartTime  string `json:"startTime"`
			EndTime    string `json:"endTime"`
			CourseID   int64  `json:"courseId"`
			CourseName string `json:"courseName"`
			IsActive   bool   `json:"isActive"`
		}

		var req struct {
			Schedules []reqSchedule `json:"schedules"`
		}

		if err := c.BodyParser(&req); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload format"})
		}

		tx := db.Begin()
		// Xóa lịch cũ
		if err := tx.Where("class_rk_id = ?", rkID).Delete(&models.ClassSchedule{}).Error; err != nil {
			tx.Rollback()
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to clear old schedules"})
		}

		// Insert lịch mới
		if len(req.Schedules) > 0 {
			var newSchedules []models.ClassSchedule
			for _, s := range req.Schedules {
				if strings.TrimSpace(s.StartTime) == "" || strings.TrimSpace(s.EndTime) == "" {
					continue
				}
				period := s.Period
				if period < 1 {
					period = 1
				}
				if period > 4 {
					period = 4
				}
				newSchedules = append(newSchedules, models.ClassSchedule{
					ClassRkID:  rkID,
					DayOfWeek:  s.DayOfWeek,
					Period:     period,
					StartTime:  s.StartTime,
					EndTime:    s.EndTime,
					CourseID:   s.CourseID,
					CourseName: s.CourseName,
					IsActive:   s.IsActive,
				})
			}
			if err := tx.Create(&newSchedules).Error; err != nil {
				tx.Rollback()
				return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to insert new schedules"})
			}
		}

		if err := tx.Commit().Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Transaction commit failed"})
		}

		return c.JSON(fiber.Map{"ok": true, "message": "Schedule saved successfully"})
	}
}

// DELETE /api/classes/:rkId/schedule - Xóa lịch học của lớp (rút lớp khỏi active list)
func DeleteClassScheduleHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		rkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
		}

		if err := db.Where("class_rk_id = ?", rkID).Delete(&models.ClassSchedule{}).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		return c.JSON(fiber.Map{"ok": true, "message": "Schedule deleted successfully"})
	}
}

func parseTimeMinutes(tStr string) int {
	parts := strings.Split(tStr, ":")
	if len(parts) != 2 {
		return 0
	}
	hours, _ := strconv.Atoi(parts[0])
	mins, _ := strconv.Atoi(parts[1])
	return hours*60 + mins
}

// GET /api/classes/:rkId/allowed-apps - Lấy danh sách app được phép
func GetAllowedAppsHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		rkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
		}

		studentIDStr := c.Query("studentId", "0")
		studentID, _ := strconv.ParseInt(studentIDStr, 10, 64)

		// Phòng thi ưu tiên hơn lịch học
		if studentID > 0 {
			if examInfo := internalDb.FindActiveExamForStudent(db, studentID); examInfo != nil {
				keywords := strings.TrimSpace(examInfo.Room.AllowedApps)
				if keywords == "" {
					keywords = defaultExamAllowedApps
				}
				return c.JSON(fiber.Map{
					"classRkId": rkID,
					"keywords":  keywords,
					"exit":      false,
					"examMode":  true,
					"examRoomId": examInfo.Room.ID,
				})
			}
		}

		if studentID > 0 {
			resolvedClassID := internalDb.FindActiveClassForStudent(db, studentID)
			if resolvedClassID > 0 {
				rkID = resolvedClassID
			}
		}

		// 1. Kiểm tra xem lớp đã cấu hình app được phép chưa
		var appConfig models.ClassAllowedApp
		errApp := db.Where("class_rk_id = ?", rkID).First(&appConfig).Error
		hasAppsConfigured := (errApp == nil && strings.TrimSpace(appConfig.Keywords) != "")

		// 2. Kiểm tra xem có trong giờ học không
		var schedules []models.ClassSchedule
		errSched := db.Where("class_rk_id = ?", rkID).Find(&schedules).Error
		inSchedule := false
		if errSched == nil && len(schedules) > 0 {
			now := time.Now()
			goWeekday := now.Weekday()
			var scheduleDay int
			if goWeekday == time.Sunday {
				scheduleDay = 6
			} else {
				scheduleDay = int(goWeekday) - 1
			}
			currMin := now.Hour()*60 + now.Minute()
			for _, s := range schedules {
				if s.DayOfWeek == scheduleDay && s.IsActive {
					startMin := parseTimeMinutes(s.StartTime)
					endMin := parseTimeMinutes(s.EndTime)
					if currMin >= startMin && currMin <= endMin {
						inSchedule = true
						break
					}
				}
			}
		}

		// Nếu lớp chưa cấu hình app được phép, hoặc không trong giờ học
		if !hasAppsConfigured || !inSchedule {
			// Tắt trạng thái đang học của lớp trong DB (nếu đang bật)
			var cl models.Class
			if errClass := db.Where("rk_id = ?", rkID).First(&cl).Error; errClass == nil {
				if cl.IsStudying {
					cl.IsStudying = false
					db.Save(&cl)
				}
			}

			reason := ""
			if !hasAppsConfigured {
				reason = "Lớp chưa được xét danh sách ứng dụng được phép."
			} else {
				reason = "Hiện tại ngoài giờ học theo lịch đã cài đặt."
			}

			return c.JSON(fiber.Map{
				"classRkId": rkID,
				"keywords":  "",
				"exit":      true,
				"reason":    reason,
			})
		}

		return c.JSON(appConfig)
	}
}

// POST /api/classes/:rkId/allowed-apps - Lưu danh sách app được phép
func SaveAllowedAppsHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		rkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
		}

		var req struct {
			Keywords string `json:"keywords"`
		}
		if err := c.BodyParser(&req); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload format"})
		}

		appConfig := models.ClassAllowedApp{
			ClassRkID: rkID,
			Keywords:  req.Keywords,
		}

		if err := db.Save(&appConfig).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		return c.JSON(fiber.Map{"ok": true, "data": appConfig})
	}
}

// POST /api/student/sync-log - Nhận log học tập (online/offline time, wifi SSID) tích lũy từ client
func SyncStudentSessionLogHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		var req struct {
			StudentRkID    int64  `json:"studentRkId"`
			ClassRkID      int64  `json:"classRkId"`
			SessionDate    string `json:"sessionDate"` // YYYY-MM-DD
			AddOnlineSecs  int    `json:"addOnlineSeconds"`
			AddOfflineSecs int    `json:"addOfflineSeconds"`
			WifiSSID       string `json:"wifiSsid"`
			Avatar         string `json:"avatar"`
		}

		if err := c.BodyParser(&req); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload format"})
		}

		if req.StudentRkID == 0 || req.SessionDate == "" {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing key parameters"})
		}

		// Dynamically resolve active class + ca theo lịch học
		resolvedClassID, resolvedPeriod := internalDb.FindActiveClassAndPeriodForStudent(db, req.StudentRkID)
		if resolvedClassID > 0 {
			req.ClassRkID = resolvedClassID
		}
		period := resolvedPeriod
		if period < 1 {
			period = 1
		}

		if req.ClassRkID == 0 {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Student does not belong to any class"})
		}

		if avatar := util.NormalizeAvatarURL(req.Avatar); avatar != "" {
			_ = db.Model(&models.Student{}).Where("rk_id = ?", req.StudentRkID).Update("avatar", avatar).Error
		}

		var session models.StudentSession
		err := db.Where("student_rk_id = ? AND class_rk_id = ? AND session_date = ? AND period = ?",
			req.StudentRkID, req.ClassRkID, req.SessionDate, period).First(&session).Error

		now := time.Now()
		if gorm.ErrRecordNotFound == err {
			// Tạo record mới
			session = models.StudentSession{
				StudentRkID:    req.StudentRkID,
				ClassRkID:      req.ClassRkID,
				SessionDate:    req.SessionDate,
				Period:         period,
				OnlineSeconds:  req.AddOnlineSecs,
				OfflineSeconds: req.AddOfflineSecs,
				WifiSSIDs:      req.WifiSSID,
				LastActiveAt:   now,
			}
			if err := db.Create(&session).Error; err != nil {
				return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create student session: " + err.Error()})
			}
		} else if err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		} else {
			// Cộng dồn thời gian
			session.OnlineSeconds += req.AddOnlineSecs
			session.OfflineSeconds += req.AddOfflineSecs
			session.LastActiveAt = now

			// Thêm Wifi SSID nếu chưa tồn tại trong danh sách
			if req.WifiSSID != "" {
				ssids := strings.Split(session.WifiSSIDs, ",")
				exists := false
				for _, s := range ssids {
					if strings.TrimSpace(s) == req.WifiSSID {
						exists = true
						break
					}
				}
				if !exists {
					if session.WifiSSIDs == "" {
						session.WifiSSIDs = req.WifiSSID
					} else {
						session.WifiSSIDs = session.WifiSSIDs + "," + req.WifiSSID
					}
				}
			}

			if err := db.Save(&session).Error; err != nil {
				return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to update student session: " + err.Error()})
			}
		}

		return c.JSON(fiber.Map{"ok": true, "session": session})
	}
}

type studentSessionLogItem struct {
	ID             uint      `json:"id"`
	StudentRkID    int64     `json:"studentRkId"`
	StudentCode    string    `json:"studentCode"`
	FullName       string    `json:"fullName"`
	Email          string    `json:"email"`
	Period         int       `json:"period"`
	OnlineSeconds  int       `json:"onlineSeconds"`
	OfflineSeconds int       `json:"offlineSeconds"`
	WifiSSIDs      string    `json:"wifiSsids"`
	LastActiveAt   time.Time `json:"lastActiveAt"`
}

// GET /api/classes/:rkId/session-logs - Lấy danh sách logs điểm danh của lớp trong ngày
func ListClassSessionLogsHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		classRkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
		}

		date := c.Query("date")
		if date == "" {
			date = time.Now().Format("2006-01-02")
		}
		period := atoiDefault(c.Query("period"), 0)
		if period <= 0 {
			period = internalDb.ResolveSessionPeriodForDate(db, classRkID, date, 0)
		}

		// Lấy danh sách học sinh của lớp từ class_students mapping
		var mappings []models.ClassStudent
		if err := db.Where("class_rk_id = ?", classRkID).Find(&mappings).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		if len(mappings) == 0 {
			return c.JSON(fiber.Map{"data": []any{}})
		}

		var studentRkIDs []int64
		for _, m := range mappings {
			studentRkIDs = append(studentRkIDs, m.StudentRkID)
		}

		var students []models.Student
		if err := db.Where("rk_id IN ?", studentRkIDs).Find(&students).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		// Lấy log session theo ca trong ngày
		var sessions []models.StudentSession
		periods := []int{period}
		if period == 1 {
			periods = append(periods, 0) // dữ liệu cũ gộp vào ca 1
		}
		if err := db.Where("class_rk_id = ? AND session_date = ? AND student_rk_id IN ? AND period IN ?",
			classRkID, date, studentRkIDs, periods).Find(&sessions).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		sessionsMap := map[int64]models.StudentSession{}
		for _, s := range sessions {
			existing, ok := sessionsMap[s.StudentRkID]
			if !ok || s.Period > existing.Period {
				sessionsMap[s.StudentRkID] = s
			}
		}

		out := make([]studentSessionLogItem, 0, len(students))
		for _, st := range students {
			sess, ok := sessionsMap[st.RkID]
			onlineSec := 0
			offlineSec := 0
			wifi := "—"
			var lastAct time.Time
			if ok {
				onlineSec = sess.OnlineSeconds
				offlineSec = sess.OfflineSeconds
				wifi = sess.WifiSSIDs
				lastAct = sess.LastActiveAt
			}

			out = append(out, studentSessionLogItem{
				ID:             sess.ID,
				StudentRkID:    st.RkID,
				StudentCode:    st.StudentCode,
				FullName:       st.FullName,
				Email:          st.Email,
				Period:         period,
				OnlineSeconds:  onlineSec,
				OfflineSeconds: offlineSec,
				WifiSSIDs:      wifi,
				LastActiveAt:   lastAct,
			})
		}

		return c.JSON(fiber.Map{"data": out, "period": period, "date": date})
	}
}

// GET /api/classes/:rkId/online-students - Lấy danh sách ID sinh viên đang online
func GetOnlineStudentsHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		classRkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
		}

		onlineIDs := internalWs.Hub.GetOnlineStudentIDs(classRkID)
		return c.JSON(fiber.Map{"onlineStudentIds": onlineIDs})
	}
}

func processKeyword(processName string) string {
	name := strings.ToLower(strings.TrimSpace(processName))
	return strings.TrimSuffix(name, ".exe")
}

func processKey(processName string) string {
	return strings.ToLower(strings.TrimSpace(processName))
}

// POST /api/student/report-blocked-app — client báo app vừa bị chặn → kho global
func ReportBlockedAppHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		var req struct {
			StudentRkID int64  `json:"studentRkId"`
			ProcessName string `json:"processName"`
			WindowTitle string `json:"windowTitle"`
		}
		if err := c.BodyParser(&req); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload format"})
		}
		if req.StudentRkID <= 0 || strings.TrimSpace(req.ProcessName) == "" {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "studentRkId and processName are required"})
		}

		processName := strings.TrimSpace(req.ProcessName)
		pKey := processKey(processName)
		keyword := processKeyword(processName)
		if pKey == "" || keyword == "" {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid processName"})
		}

		lastClassID := internalDb.FindActiveClassForStudent(db, req.StudentRkID)
		now := time.Now()

		var row models.AppPoolEntry
		err := db.Where("process_key = ?", pKey).First(&row).Error
		if err == gorm.ErrRecordNotFound {
			row = models.AppPoolEntry{
				ProcessName:     processName,
				ProcessKey:      pKey,
				WindowTitle:     req.WindowTitle,
				Keyword:         keyword,
				HitCount:        1,
				LastSeenAt:      now,
				LastStudentRkID: req.StudentRkID,
				LastClassRkID:   lastClassID,
			}
			if err := db.Create(&row).Error; err != nil {
				return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
			}
		} else if err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		} else {
			row.HitCount++
			row.LastSeenAt = now
			row.WindowTitle = req.WindowTitle
			row.ProcessName = processName
			if req.StudentRkID > 0 {
				row.LastStudentRkID = req.StudentRkID
			}
			if lastClassID > 0 {
				row.LastClassRkID = lastClassID
			}
			if err := db.Save(&row).Error; err != nil {
				return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
			}
		}

		return c.JSON(fiber.Map{"ok": true, "data": row})
	}
}

// GET /api/app-pool — kho app bị chặn toàn hệ thống (?q= tìm kiếm, limit= mặc định 50)
func ListAppPoolHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		q := strings.TrimSpace(c.Query("q", ""))
		limit := 50
		if l, err := strconv.Atoi(c.Query("limit", "50")); err == nil && l > 0 && l <= 200 {
			limit = l
		}

		query := db.Model(&models.AppPoolEntry{})
		if q != "" {
			like := "%" + q + "%"
			query = query.Where(
				"keyword LIKE ? OR process_name LIKE ? OR window_title LIKE ? OR process_key LIKE ?",
				like, like, like, like,
			)
		}

		var rows []models.AppPoolEntry
		if err := query.Order("hit_count DESC, last_seen_at DESC").Limit(limit).Find(&rows).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}
		return c.JSON(fiber.Map{"data": rows, "q": q, "limit": limit})
	}
}
