package handlers

import (
	"context"
	"fmt"
	"strconv"
	"time"

	internalDb "server/internal/db"
	"server/internal/models"
	"server/internal/qldt"

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

var standardShiftTemplate = []struct {
	Period    int
	StartTime string
	EndTime   string
}{
	{1, "07:00", "09:00"},
	{2, "09:10", "11:10"},
	{3, "12:10", "14:10"},
	{4, "14:20", "16:20"},
}

// GET /api/classes/:rkId/courses
func GetClassCoursesHandler(db *gorm.DB, qldtClient *qldt.Client, token string) 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 liveErr error
		if token != "" {
			res, err := qldtClient.GetClassCourses(context.Background(), token, rkID)
			if err == nil && len(res.Data) > 0 {
				dtos := qldtCourseItemsToDTO(res.Data)
				_ = internalDb.UpsertClassCourses(db, rkID, dtos)
				return c.JSON(fiber.Map{"data": dtos, "source": "qldt"})
			}
			liveErr = err
		} else {
			liveErr = fmt.Errorf("QLDT_TOKEN chưa cấu hình")
		}

		cached, err := internalDb.ListClassCourses(db, rkID)
		if err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}
		if len(cached) > 0 {
			resp := fiber.Map{
				"data":   cached,
				"source": "cache",
				"cached": true,
			}
			if liveErr != nil {
				resp["warning"] = liveErr.Error()
			}
			return c.JSON(resp)
		}

		if liveErr != nil {
			return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{
				"error": liveErr.Error(),
				"hint":  "Cập nhật QLDT_TOKEN trong server/.env rồi chạy Sync lớp học để tải danh sách môn.",
			})
		}
		return c.JSON(fiber.Map{"data": []internalDb.ClassCourseDTO{}})
	}
}

func qldtCourseItemsToDTO(items []qldt.CourseItem) []internalDb.ClassCourseDTO {
	out := make([]internalDb.ClassCourseDTO, 0, len(items))
	for _, it := range items {
		if it.ID <= 0 {
			continue
		}
		out = append(out, internalDb.ClassCourseDTO{
			ID:         it.ID,
			Name:       it.Name,
			CourseCode: it.CourseCode,
		})
	}
	return out
}

// POST /api/classes/:rkId/schedule/apply-template
func ApplyScheduleTemplateHandler(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"})
		}

		created := 0
		skipped := 0
		for day := 0; day <= 4; day++ {
			for _, slot := range standardShiftTemplate {
				var existing int64
				db.Model(&models.ClassSchedule{}).
					Where("class_rk_id = ? AND day_of_week = ? AND period = ?", rkID, day, slot.Period).
					Count(&existing)
				if existing > 0 {
					skipped++
					continue
				}
				row := models.ClassSchedule{
					ClassRkID:  rkID,
					DayOfWeek:  day,
					Period:     slot.Period,
					StartTime:  slot.StartTime,
					EndTime:    slot.EndTime,
					CourseID:   0,
					CourseName: "",
					IsActive:   false,
				}
				if err := db.Create(&row).Error; err != nil {
					return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
				}
				created++
			}
		}
		return c.JSON(fiber.Map{"ok": true, "created": created, "skipped": skipped})
	}
}

type attendanceRow struct {
	StudentRkID           int64      `json:"studentRkId"`
	StudentCode           string     `json:"studentCode"`
	FullName              string     `json:"fullName"`
	Email                 string     `json:"email"`
	Status                int        `json:"status"`
	StatusLabel           string     `json:"statusLabel"`
	OnlineMinutes         int        `json:"onlineMinutes"`
	StatusEditedByTeacher bool       `json:"statusEditedByTeacher"`
	PushedToQLDTAt        *time.Time `json:"pushedToQldtAt,omitempty"`
}

func resolveScheduleForPeriod(db *gorm.DB, classRkID int64, dayOfWeek, period int) (*models.ClassSchedule, error) {
	var row models.ClassSchedule
	err := db.Where("class_rk_id = ? AND day_of_week = ? AND period = ?", classRkID, dayOfWeek, period).First(&row).Error
	if err == nil {
		return &row, nil
	}
	var slots []models.ClassSchedule
	if err := db.Where("class_rk_id = ? AND day_of_week = ?", classRkID, dayOfWeek).
		Order("start_time").Find(&slots).Error; err != nil {
		return nil, err
	}
	if period < 1 || period > len(slots) {
		return nil, gorm.ErrRecordNotFound
	}
	return &slots[period-1], nil
}

func recalcAttendanceForPeriod(db *gorm.DB, classRkID int64, date string, period int, dayOfWeek int) error {
	sched, err := resolveScheduleForPeriod(db, classRkID, dayOfWeek, period)
	if err != nil {
		return err
	}
	shiftMins := qldt.ShiftDurationMinutes(sched.StartTime, sched.EndTime)

	var mappings []models.ClassStudent
	if err := db.Where("class_rk_id = ?", classRkID).Find(&mappings).Error; err != nil {
		return err
	}

	for _, m := range mappings {
		var session models.StudentSession
		onlineMins := 0
		periods := []int{period}
		if period == 1 {
			periods = append(periods, 0)
		}
		err := db.Where("student_rk_id = ? AND class_rk_id = ? AND session_date = ? AND period IN ?",
			m.StudentRkID, classRkID, date, periods).
			Order("period DESC").First(&session).Error
		if err == nil {
			onlineMins = session.OnlineSeconds / 60
		}

		status := qldt.InferStatusFromOnlineMinutes(onlineMins, shiftMins)
		label := qldt.AttendanceStatusLabel(status)

		row := models.AttendanceResult{
			ClassRkID:     classRkID,
			SessionDate:   date,
			Period:        period,
			StudentRkID:   m.StudentRkID,
			Status:        status,
			StatusLabel:   label,
			OnlineMinutes: onlineMins,
		}

		if err := db.Clauses(clause.OnConflict{
			Columns: []clause.Column{
				{Name: "class_rk_id"},
				{Name: "session_date"},
				{Name: "period"},
				{Name: "student_rk_id"},
			},
			DoUpdates: clause.Assignments(map[string]interface{}{
				"online_minutes": gorm.Expr("VALUES(online_minutes)"),
				"status":         gorm.Expr("IF(status_edited_by_teacher, status, VALUES(status))"),
				"status_label":   gorm.Expr("IF(status_edited_by_teacher, status_label, VALUES(status_label))"),
				"updated_at":     time.Now(),
			}),
		}).Create(&row).Error; err != nil {
			return err
		}
	}
	return nil
}

func scheduleDayFromDate(date string) (int, error) {
	t, err := time.Parse("2006-01-02", date)
	if err != nil {
		return 0, err
	}
	wd := t.Weekday()
	if wd == time.Sunday {
		return 6, nil
	}
	return int(wd) - 1, nil
}

// GET /api/classes/:rkId/attendance?date=&period=
func GetClassAttendanceHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
		date := c.Query("date", qldt.NowDateVN())
		period, _ := strconv.Atoi(c.Query("period", "1"))
		if period < 1 {
			period = 1
		}

		dayOfWeek, err := scheduleDayFromDate(date)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid date"})
		}

		_ = recalcAttendanceForPeriod(db, classRkID, date, period, dayOfWeek)

		var mappings []models.ClassStudent
		db.Where("class_rk_id = ?", classRkID).Find(&mappings)
		studentIDs := make([]int64, 0, len(mappings))
		for _, m := range mappings {
			studentIDs = append(studentIDs, m.StudentRkID)
		}
		if len(studentIDs) == 0 {
			return c.JSON(fiber.Map{"data": []any{}, "period": period, "date": date})
		}

		var students []models.Student
		db.Where("rk_id IN ?", studentIDs).Find(&students)

		var results []models.AttendanceResult
		db.Where("class_rk_id = ? AND session_date = ? AND period = ?", classRkID, date, period).Find(&results)
		resMap := map[int64]models.AttendanceResult{}
		for _, r := range results {
			resMap[r.StudentRkID] = r
		}

		out := make([]attendanceRow, 0, len(students))
		for _, st := range students {
			r, ok := resMap[st.RkID]
			row := attendanceRow{
				StudentRkID: st.RkID,
				StudentCode: st.StudentCode,
				FullName:    st.FullName,
				Email:       st.Email,
			}
			if ok {
				row.Status = r.Status
				row.StatusLabel = r.StatusLabel
				row.OnlineMinutes = r.OnlineMinutes
				row.StatusEditedByTeacher = r.StatusEditedByTeacher
				row.PushedToQLDTAt = r.PushedToQLDTAt
			}
			out = append(out, row)
		}

		var shiftPushedAt *time.Time
		qldtDirty := false
		for _, r := range results {
			if r.PushedToQLDTAt != nil {
				if shiftPushedAt == nil || r.PushedToQLDTAt.After(*shiftPushedAt) {
					t := *r.PushedToQLDTAt
					shiftPushedAt = &t
				}
				if r.UpdatedAt.After(*r.PushedToQLDTAt) {
					qldtDirty = true
				}
			}
		}

		sched, _ := resolveScheduleForPeriod(db, classRkID, dayOfWeek, period)
		shiftInfo := fiber.Map{
			"pushedToQldtAt": shiftPushedAt,
			"qldtDirty":      qldtDirty,
		}
		if sched != nil {
			shiftInfo["startTime"] = sched.StartTime
			shiftInfo["endTime"] = sched.EndTime
			shiftInfo["courseId"] = sched.CourseID
			shiftInfo["courseName"] = sched.CourseName
			shiftInfo["isActive"] = sched.IsActive
		}

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

// PUT /api/classes/:rkId/attendance/status
func UpdateAttendanceStatusHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
		var req struct {
			Date        string `json:"date"`
			Period      int    `json:"period"`
			StudentRkID int64  `json:"studentRkId"`
			Status      int    `json:"status"`
		}
		if err := c.BodyParser(&req); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
		}
		if req.Date == "" {
			req.Date = qldt.NowDateVN()
		}
		if req.Period < 1 {
			req.Period = 1
		}

		label := qldt.AttendanceStatusLabel(req.Status)
		var row models.AttendanceResult
		err := db.Where("class_rk_id = ? AND session_date = ? AND period = ? AND student_rk_id = ?",
			classRkID, req.Date, req.Period, req.StudentRkID).First(&row).Error
		if err == gorm.ErrRecordNotFound {
			row = models.AttendanceResult{
				ClassRkID:             classRkID,
				SessionDate:           req.Date,
				Period:                req.Period,
				StudentRkID:           req.StudentRkID,
				Status:                req.Status,
				StatusLabel:           label,
				StatusEditedByTeacher: true,
			}
			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.Status = req.Status
			row.StatusLabel = label
			row.StatusEditedByTeacher = true
			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})
	}
}

// POST /api/classes/:rkId/attendance/push-qldt
func PushAttendanceToQLDTHandler(db *gorm.DB, qldtClient *qldt.Client, token string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
		var req struct {
			Date   string `json:"date"`
			Period int    `json:"period"`
		}
		if err := c.BodyParser(&req); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
		}
		if req.Date == "" {
			req.Date = qldt.NowDateVN()
		}
		if req.Period < 1 {
			req.Period = 1
		}
		if token == "" {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "QLDT_TOKEN chưa cấu hình"})
		}

		dayOfWeek, _ := scheduleDayFromDate(req.Date)
		sched, err := resolveScheduleForPeriod(db, classRkID, dayOfWeek, req.Period)
		if err != nil || sched == nil || sched.CourseID == 0 {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Ca học chưa cấu hình môn học (courseId)"})
		}

		userID, err := qldt.ParseUserIDFromToken(token)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Không đọc được users_id từ QLDT_TOKEN: " + err.Error()})
		}

		var results []models.AttendanceResult
		db.Where("class_rk_id = ? AND session_date = ? AND period = ?", classRkID, req.Date, req.Period).Find(&results)
		if len(results) == 0 {
			_ = recalcAttendanceForPeriod(db, classRkID, req.Date, req.Period, dayOfWeek)
			db.Where("class_rk_id = ? AND session_date = ? AND period = ?", classRkID, req.Date, req.Period).Find(&results)
		}

		students := make([]qldt.AttendanceStudent, 0, len(results))
		for _, r := range results {
			students = append(students, qldt.AttendanceStudent{
				StudentID: r.StudentRkID,
				Status:    r.Status,
			})
		}

		payload := &qldt.AttendancePayload{
			UsersID:   userID,
			CoursesID: int(sched.CourseID),
			Period:    req.Period,
			Students:  students,
			ClassID:   classRkID,
			Date:      req.Date,
			Type:      "OFFLINE",
		}

		body, code, err := qldtClient.SaveAttendance(context.Background(), token, payload)
		if err != nil {
			return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error(), "statusCode": code, "body": string(body)})
		}

		now := time.Now()
		db.Model(&models.AttendanceResult{}).
			Where("class_rk_id = ? AND session_date = ? AND period = ?", classRkID, req.Date, req.Period).
			Update("pushed_to_qldt_at", now)

		return c.JSON(fiber.Map{"ok": true, "message": "Đã đẩy điểm danh lên QLĐT", "studentCount": len(students)})
	}
}

// GET /api/classes/:rkId/attendance/shifts?date=
func ListAttendanceShiftsHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
		date := c.Query("date", qldt.NowDateVN())
		dayOfWeek, err := scheduleDayFromDate(date)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid date"})
		}

		var slots []models.ClassSchedule
		db.Where("class_rk_id = ? AND day_of_week = ?", classRkID, dayOfWeek).
			Order("period, start_time").Find(&slots)

		if len(slots) == 0 {
			for p := 1; p <= 4; p++ {
				slots = append(slots, models.ClassSchedule{
					Period: p, DayOfWeek: dayOfWeek, IsActive: false,
				})
			}
		}

		out := make([]fiber.Map, 0, len(slots))
		for _, s := range slots {
			period := s.Period
			if period == 0 {
				period = 1
			}
			out = append(out, fiber.Map{
				"period":     period,
				"startTime":  s.StartTime,
				"endTime":    s.EndTime,
				"courseId":   s.CourseID,
				"courseName": s.CourseName,
				"isActive":   s.IsActive,
			})
		}
		return c.JSON(fiber.Map{"data": out, "date": date, "dayOfWeek": dayOfWeek})
	}
}
