package controllers

import (
	"context"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"time"

	"doormile/config"
	"doormile/constants"
	"doormile/db"
	"doormile/dto"
	"doormile/internal/assignment"
	"doormile/internal/notify"
	"doormile/models"
	"doormile/utils"

	"github.com/gofiber/fiber/v2"
	"github.com/redis/go-redis/v9"
)

func generateTrackingNo() string {
	b := make([]byte, 4)
	rand.Read(b)
	return fmt.Sprintf("DM-TRK-%X-%d", b, time.Now().Unix()%100000)
}

func LoginMiler(cfg *config.Config) fiber.Handler {
	return func(c *fiber.Ctx) error {
		req := new(dto.MilerLoginRequest)
		if err := c.BodyParser(req); err != nil {
			return utils.BadRequest(c, "invalid request body")
		}

		if req.Phone == "" {
			return utils.BadRequest(c, "phone is required")
		}

		configID := req.Configid
		if configID == 0 {
			configID = 1001
		}

		var user models.AppUser
		if err := db.DB.Where("contactno = ? AND configid = ?", req.Phone, configID).First(&user).Error; err != nil {
			return utils.NotFound(c, "no miler account found for this phone number")
		}

		if user.Roleid != 5 {
			return utils.Forbidden(c, "this endpoint is restricted to miler accounts")
		}

		if user.Status != "Active" {
			return utils.Forbidden(c, "miler account is not active")
		}

		return c.JSON(fiber.Map{
			"success": true,
			"message": "PIN verification required",
			"phone":   req.Phone,
		})
	}
}

func VerifyMilerPin(cfg *config.Config) fiber.Handler {
	return func(c *fiber.Ctx) error {
		req := new(dto.MilerPinVerifyRequest)
		if err := c.BodyParser(req); err != nil {
			return utils.BadRequest(c, "invalid request body")
		}

		if req.Phone == "" || req.Pin == "" {
			return utils.BadRequest(c, "phone and pin are required")
		}

		configID := req.Configid
		if configID == 0 {
			configID = 1001
		}

		var user models.AppUser
		if err := db.DB.Where("contactno = ? AND configid = ?", req.Phone, configID).First(&user).Error; err != nil {
			return utils.NotFound(c, "no miler account found for this phone number")
		}

		if user.Roleid != 5 {
			return utils.Forbidden(c, "this endpoint is restricted to miler accounts")
		}

		if user.Status != "Active" {
			return utils.Forbidden(c, "miler account is not active")
		}

		if !utils.CheckPasswordHash(req.Pin, user.Password) {
			return utils.Unauthorized(c, "incorrect PIN")
		}

		token, err := utils.GenerateToken(user.Userid, user.Email, user.Roleid, user.Tenantid, user.Configid, cfg.JWTSecret)
		if err != nil {
			return utils.Internal(c, "failed to generate token")
		}

		var profile models.MilerProfile
		if err := db.DB.Where("userid = ?", user.Userid).First(&profile).Error; err != nil {
			profile = models.MilerProfile{
				Userid:             user.Userid,
				Displayname:        user.Authname,
				Phone:              user.Contactno,
				Availabilitystatus: constants.MilerOffline,
				Rating:             5.0,
				Applocationid:      user.Applocationid,
			}
			db.DB.Create(&profile)
		}
		if req.DeviceToken != "" && profile.Devicetoken != req.DeviceToken {
			profile.Devicetoken = req.DeviceToken
			db.DB.Model(&profile).Update("device_token", req.DeviceToken)
		}

		return c.JSON(fiber.Map{
			"success": true,
			"token":   token,
			"user": fiber.Map{
				"userid":    user.Userid,
				"authname":  user.Authname,
				"email":     user.Email,
				"contactno": user.Contactno,
				"profile":   profile,
			},
		})
	}
}

func GetMilerProfile(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var user models.AppUser
	if err := db.DB.First(&user, milerUserID).Error; err != nil {
		return utils.NotFound(c, "user not found")
	}

	var profile models.MilerProfile
	if err := db.DB.Where("userid = ?", milerUserID).First(&profile).Error; err != nil {
		return utils.NotFound(c, "miler profile not found")
	}

	return utils.OK(c, fiber.Map{
		"userid":    user.Userid,
		"authname":  user.Authname,
		"email":     user.Email,
		"contactno": user.Contactno,
		"profile":   profile,
	})
}

func UpdateMilerProfile(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var profile models.MilerProfile
	if err := db.DB.Where("userid = ?", milerUserID).First(&profile).Error; err != nil {
		return utils.NotFound(c, "miler profile not found")
	}

	type ProfileUpdate struct {
		Displayname        string `json:"displayname"`
		Profilephotourl    string `json:"profilephotourl"`
		Defaultvehicletype string `json:"defaultvehicletype"`
		Phone              string `json:"phone"`
	}

	req := new(ProfileUpdate)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	if req.Displayname != "" {
		profile.Displayname = req.Displayname
	}
	if req.Phone != "" {
		profile.Phone = req.Phone
	}
	profile.Profilephotourl = req.Profilephotourl
	profile.Defaultvehicletype = req.Defaultvehicletype
	profile.Updatedat = time.Now()

	if err := db.DB.Save(&profile).Error; err != nil {
		return utils.Internal(c, "failed to update profile")
	}

	return utils.OK(c, profile)
}

func UpdateMilerLocation(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	req := new(dto.MilerLocationUpdateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	if req.Latitude == 0 || req.Longitude == 0 {
		return utils.BadRequest(c, "latitude and longitude are required")
	}

	var profile models.MilerProfile
	if err := db.DB.Where("userid = ?", milerUserID).First(&profile).Error; err != nil {
		return utils.NotFound(c, "miler profile not found")
	}

	profile.Currentlatitude = req.Latitude
	profile.Currentlongitude = req.Longitude
	profile.Currentpincode = req.Pincode
	now := time.Now()
	profile.Lastlocationupdatedat = &now
	profile.Updatedat = now

	if err := db.DB.Save(&profile).Error; err != nil {
		return utils.Internal(c, "failed to update location")
	}

	if db.Rdb != nil {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()

		redisKey := fmt.Sprintf("miler:gps:%d", milerUserID)
		val := fmt.Sprintf("%f,%f", req.Latitude, req.Longitude)
		db.Rdb.Set(ctx, redisKey, val, 30*time.Minute)

		db.Rdb.GeoAdd(ctx, "milers:locations", &redis.GeoLocation{
			Name:      strconv.Itoa(milerUserID),
			Latitude:  req.Latitude,
			Longitude: req.Longitude,
		})
	}

	return utils.OK(c, fiber.Map{
		"latitude":  req.Latitude,
		"longitude": req.Longitude,
		"pincode":   req.Pincode,
	})
}

func UpdateMilerAvailability(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	req := new(dto.MilerAvailabilityRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	if req.Status == "" {
		return utils.BadRequest(c, "status is required")
	}

	var profile models.MilerProfile
	if err := db.DB.Where("userid = ?", milerUserID).First(&profile).Error; err != nil {
		return utils.NotFound(c, "miler profile not found")
	}

	profile.Availabilitystatus = req.Status
	profile.Updatedat = time.Now()
	if err := db.DB.Save(&profile).Error; err != nil {
		return utils.Internal(c, "failed to update availability")
	}

	var user models.AppUser
	if err := db.DB.First(&user, milerUserID).Error; err == nil {
		if req.Status == constants.MilerOffline || req.Status == constants.MilerBlocked {
			user.Onduty = 0
		} else {
			user.Onduty = 1
		}
		db.DB.Save(&user)
	}

	return utils.OK(c, profile)
}

func GetMilerAssignments(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var assignments []models.BookingAssignment
	if err := db.DB.Where("mileruserid = ? AND assignmentstatus IN ?", milerUserID,
		[]string{constants.AssignmentAssigned, constants.AssignmentAccepted}).
		Order("assignedat DESC").Find(&assignments).Error; err != nil {
		return utils.Internal(c, "failed to fetch assignments")
	}

	return utils.List(c, assignments, int64(len(assignments)))
}

func GetMilerAssignmentDetails(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	assignmentID, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return utils.BadRequest(c, "invalid assignment ID")
	}

	var assignment models.BookingAssignment
	if err := db.DB.Where("bookingassignmentid = ? AND mileruserid = ?", assignmentID, milerUserID).First(&assignment).Error; err != nil {
		return utils.NotFound(c, "assignment not found")
	}

	var booking models.PickupBooking
	db.DB.Preload("Parcels").Preload("ServiceOptions").First(&booking, assignment.Bookingid)

	return utils.OK(c, fiber.Map{
		"assignment": assignment,
		"booking":    booking,
	})
}

func AcceptMilerAssignment(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	assignmentID, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return utils.BadRequest(c, "invalid assignment ID")
	}

	tx := db.DB.Begin()

	var assignment models.BookingAssignment
	if err := tx.Where("bookingassignmentid = ? AND mileruserid = ?", assignmentID, milerUserID).First(&assignment).Error; err != nil {
		tx.Rollback()
		return utils.NotFound(c, "assignment not found")
	}

	if assignment.Assignmentstatus != constants.AssignmentAssigned {
		tx.Rollback()
		return utils.BadRequest(c, fmt.Sprintf("assignment is not pending acceptance (current status: %s)", assignment.Assignmentstatus))
	}

	now := time.Now()
	assignment.Assignmentstatus = constants.AssignmentAccepted
	assignment.Acceptedat = &now
	tx.Save(&assignment)

	var booking models.PickupBooking
	if err := tx.First(&booking, assignment.Bookingid).Error; err == nil {
		booking.Status = constants.BookingPickupScheduled
		booking.Assignedmileruserid = &milerUserID
		booking.Updatedat = now
		tx.Save(&booking)
	}

	tx.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Update("availabilitystatus", constants.MilerAssigned)

	tx.Commit()

	if booking.Bookingid != 0 {
		var customer models.AppCustomer
		if err := db.DB.Where("appcustomerid = ?", booking.Appcustomerid).First(&customer).Error; err == nil && customer.Devicetoken != "" {
			if notifyErr := notify.SendToDevice(
				customer.Devicetoken,
				"Miler Accepted",
				"Your miler has accepted and is coming",
				map[string]string{"booking_id": strconv.Itoa(booking.Bookingid)},
			); notifyErr != nil {
				utils.Warn("FCM: failed to notify customer on accept", "booking_id", booking.Bookingid, "error", notifyErr)
			}
		}
	}

	return utils.Message(c, "assignment accepted successfully")
}

func RejectMilerAssignment(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	assignmentID, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return utils.BadRequest(c, "invalid assignment ID")
	}

	var req struct {
		Reason string `json:"reason"`
	}
	if err := c.BodyParser(&req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if req.Reason == "" {
		req.Reason = "Rejected by rider"
	}

	tx := db.DB.Begin()

	var ba models.BookingAssignment
	if err := tx.Where("bookingassignmentid = ? AND mileruserid = ?", assignmentID, milerUserID).First(&ba).Error; err != nil {
		tx.Rollback()
		return utils.NotFound(c, "assignment not found")
	}

	ba.Assignmentstatus = constants.AssignmentRejected
	ba.Remarks = req.Reason
	tx.Save(&ba)

	var booking models.PickupBooking
	if err := tx.First(&booking, ba.Bookingid).Error; err == nil {
		booking.Status = constants.BookingCreated
		booking.Assignedmileruserid = nil
		booking.Updatedat = time.Now()
		tx.Save(&booking)
	}

	tx.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Update("availabilitystatus", constants.MilerAvailable)

	tx.Commit()

	if booking.Bookingid != 0 {
		if booking.Bookingsource == "CRM_Console" {
			go assignment.AssignCRMMiler(booking.Bookingid)
		} else {
			go assignment.AssignCustomerMiler(booking.Bookingid)
		}
	}

	return utils.Message(c, "assignment rejected")
}

func BookingReachedCustomer(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	bookingID, err := strconv.Atoi(c.Params("bookingid"))
	if err != nil {
		return utils.BadRequest(c, "invalid booking ID")
	}

	tx := db.DB.Begin()

	var booking models.PickupBooking
	if err := tx.Where("bookingid = ? AND assignedmileruserid = ?", bookingID, milerUserID).First(&booking).Error; err != nil {
		tx.Rollback()
		return utils.NotFound(c, "assigned booking not found")
	}

	tx.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Update("availabilitystatus", constants.MilerAtCustomer)

	tx.Commit()
	return utils.Message(c, "arrival at customer confirmed")
}

func BookingParcelConfirm(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	bookingID, err := strconv.Atoi(c.Params("bookingid"))
	if err != nil {
		return utils.BadRequest(c, "invalid booking ID")
	}

	var booking models.PickupBooking
	if err := db.DB.Where("bookingid = ? AND assignedmileruserid = ?", bookingID, milerUserID).First(&booking).Error; err != nil {
		return utils.NotFound(c, "assigned booking not found")
	}

	type ParcelUpdate struct {
		ParcelID int     `json:"parcel_id"`
		Weight   float64 `json:"weight"`
		Length   float64 `json:"length"`
		Width    float64 `json:"width"`
		Height   float64 `json:"height"`
	}
	var req struct {
		Parcels []ParcelUpdate `json:"parcels"`
	}
	if err := c.BodyParser(&req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if len(req.Parcels) == 0 {
		return utils.BadRequest(c, "parcels array is required")
	}

	var parcels []models.BookingParcel
	if err := db.DB.Where("bookingid = ?", bookingID).Find(&parcels).Error; err != nil {
		return utils.NotFound(c, "parcel details not found")
	}

	// Index loaded parcels by ID for O(1) lookup.
	parcelMap := make(map[int]*models.BookingParcel, len(parcels))
	for i := range parcels {
		parcelMap[parcels[i].Bookingparcelid] = &parcels[i]
	}

	now := time.Now()
	var totalChargeable float64

	for _, upd := range req.Parcels {
		p, ok := parcelMap[upd.ParcelID]
		if !ok {
			continue
		}
		p.Weight = upd.Weight
		p.Length = upd.Length
		p.Width = upd.Width
		p.Height = upd.Height
		p.Updatedat = now
		db.DB.Save(p)

		volumetric := calculateVolumetricWeight(upd.Length, upd.Width, upd.Height)
		totalChargeable += math.Max(upd.Weight, volumetric)
	}

	return utils.OK(c, fiber.Map{
		"parcels":               parcels,
		"total_chargeable_weight": totalChargeable,
	})
}

func BookingPaymentCollect(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	bookingID, err := strconv.Atoi(c.Params("bookingid"))
	if err != nil {
		return utils.BadRequest(c, "invalid booking ID")
	}

	var booking models.PickupBooking
	if err := db.DB.Where("bookingid = ? AND assignedmileruserid = ?", bookingID, milerUserID).First(&booking).Error; err != nil {
		return utils.NotFound(c, "assigned booking not found")
	}

	req := new(dto.PaymentRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	if req.Amount <= 0 {
		return utils.BadRequest(c, "payment amount must be greater than zero")
	}

	now := time.Now()
	payment := models.BookingPayment{
		Bookingid:         bookingID,
		Amount:            req.Amount,
		Paymentmode:       req.Paymentmode,
		Paymentstatus:     constants.PaymentStatusPaid,
		Collectedbyuserid: &milerUserID,
		Transactionref:    req.Transactionref,
		Paidat:            &now,
	}

	if err := db.DB.Create(&payment).Error; err != nil {
		return utils.Internal(c, "failed to record payment")
	}

	return utils.Created(c, payment)
}

func BookingPickupComplete(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	bookingID, err := strconv.Atoi(c.Params("bookingid"))
	if err != nil {
		return utils.BadRequest(c, "invalid booking ID")
	}

	tx := db.DB.Begin()

	var booking models.PickupBooking
	if err := tx.Where("bookingid = ? AND assignedmileruserid = ?", bookingID, milerUserID).First(&booking).Error; err != nil {
		tx.Rollback()
		return utils.NotFound(c, "assigned booking not found")
	}

	now := time.Now()
	booking.Status = constants.BookingPickedUp
	booking.Updatedat = now
	tx.Save(&booking)

	var profile models.MilerProfile
	if err := tx.Where("userid = ?", milerUserID).First(&profile).Error; err == nil {
		profile.Totalcompletedpickups += 1
		profile.Availabilitystatus = constants.MilerPickedUp
		profile.Updatedat = now
		tx.Save(&profile)
	}

	var parcels []models.BookingParcel
	tx.Where("bookingid = ?", bookingID).Find(&parcels)

	var totalDead, totalChargeable, maxL, maxW, maxH float64
	for _, p := range parcels {
		vol := calculateVolumetricWeight(p.Length, p.Width, p.Height)
		totalDead += p.Weight
		totalChargeable += math.Max(p.Weight, vol)
		if p.Length > maxL {
			maxL = p.Length
		}
		if p.Width > maxW {
			maxW = p.Width
		}
		if p.Height > maxH {
			maxH = p.Height
		}
	}
	if len(parcels) == 0 {
		totalDead = 0.5
		totalChargeable = 0.5
	}

	trackingNo := generateTrackingNo()

	var defaultHubID *int
	if profile.Hubid != nil {
		defaultHubID = profile.Hubid
	} else {
		utils.Warn("BookingPickupComplete: miler has no assigned hub, falling back to first hub row", "miler_user_id", milerUserID, "booking_id", bookingID)
		var hub models.Hub
		if tx.First(&hub).Error == nil {
			defaultHubID = &hub.Hubid
		}
	}

	// Hyperlocal shortcut: pickup and delivery in the same postal area mean
	// no hub-to-hub tripsheet leg is needed, so the same miler goes straight
	// to final-mile delivery instead of parking the consignment at the hub.
	consignmentStatus := constants.ConsignmentInwardedAtHub
	if len(booking.Pickuppincode) >= 3 && len(booking.Deliverypincode) >= 3 &&
		booking.Pickuppincode[:3] == booking.Deliverypincode[:3] {
		consignmentStatus = constants.ConsignmentOutForDelivery
	}

	consignment := models.Consignment{
		Trackingno:          trackingNo,
		Tenantid:            c.Locals("tenantid").(int),
		Pickuplatitude:      booking.Pickuplatitude,
		Pickuplongitude:     booking.Pickuplongitude,
		Deliverylatitude:    booking.Deliverylatitude,
		Deliverylongitude:   booking.Deliverylongitude,
		Pickuppincode:       booking.Pickuppincode,
		Deliverypincode:     booking.Deliverypincode,
		Length:              maxL,
		Width:               maxW,
		Height:              maxH,
		Deadweight:          totalDead,
		Volumetricweight:    totalChargeable - totalDead,
		Chargeableweight:    totalChargeable,
		Paymentmode:         "Prepaid",
		Status:              consignmentStatus,
		Estimateddeliveryat: nil,
		Createdby:           milerUserID,
		Originhubid:         defaultHubID,
		Currenthubid:        defaultHubID,
	}

	var payment models.BookingPayment
	if tx.Where("bookingid = ?", bookingID).First(&payment).Error == nil {
		if payment.Paymentstatus == constants.PaymentStatusPaid {
			consignment.Codcollected = payment.Amount
		} else {
			consignment.Codamount = payment.Amount
			consignment.Paymentmode = "COD"
		}
	}

	if err := tx.Create(&consignment).Error; err != nil {
		tx.Rollback()
		return utils.Internal(c, "failed to convert booking to consignment")
	}

	booking.Consignmentid = &consignment.Consignmentid
	booking.Status = constants.BookingConvertedConsignment
	tx.Save(&booking)

	history := models.ConsignmentHistory{
		Consignmentid: consignment.Consignmentid,
		Hubid:         defaultHubID,
		Userid:        &milerUserID,
		Eventstatus:   consignmentStatus,
		Remarks:       "Package collected by miler and converted to consignment",
	}
	tx.Create(&history)

	tx.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Update("availabilitystatus", constants.MilerAvailable)

	tx.Commit()

	var customer models.AppCustomer
	if err := db.DB.Where("appcustomerid = ?", booking.Appcustomerid).First(&customer).Error; err == nil && customer.Devicetoken != "" {
		if notifyErr := notify.SendToDevice(
			customer.Devicetoken,
			"Parcel Picked Up",
			fmt.Sprintf("Parcel picked up — Tracking No: %s", trackingNo),
			map[string]string{
				"booking_id":  strconv.Itoa(bookingID),
				"tracking_no": trackingNo,
			},
		); notifyErr != nil {
			utils.Warn("FCM: failed to notify customer on pickup", "booking_id", bookingID, "error", notifyErr)
		}
	}

	return utils.OK(c, fiber.Map{
		"tracking_no":    trackingNo,
		"consignment_id": consignment.Consignmentid,
		"booking_no":     booking.Bookingno,
	})
}

func BookingVehicleRequiredEscalate(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	bookingID, err := strconv.Atoi(c.Params("bookingid"))
	if err != nil {
		return utils.BadRequest(c, "invalid booking ID")
	}

	var booking models.PickupBooking
	if err := db.DB.Where("bookingid = ? AND assignedmileruserid = ?", bookingID, milerUserID).First(&booking).Error; err != nil {
		return utils.NotFound(c, "assigned booking not found")
	}

	reqVeh := models.BookingVehicleRequirement{
		Bookingid:           bookingID,
		Requiredvehicletype: c.Query("type", "truck"),
		Reason:              c.Query("reason", "Package is too large for bike rider"),
		Status:              "Required",
	}

	if err := db.DB.Create(&reqVeh).Error; err != nil {
		return utils.Internal(c, "failed to register vehicle requirement")
	}

	return utils.Created(c, reqVeh)
}

func CreateMilerPeriodicLog(c *fiber.Ctx) error {
	ctx := context.Background()

	var log models.MilerLog
	if err := c.BodyParser(&log); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	t, err := time.Parse("2006-01-02 15:04:05", log.LogDate)
	if err != nil {
		return utils.BadRequest(c, "invalid logdate format — expected YYYY-MM-DD HH:MM:SS")
	}

	timestamp := t.Unix()
	logKey := fmt.Sprintf("miler_periodic_log:%d:%d", log.UserID, timestamp)
	data, _ := json.Marshal(log)

	if db.Rdb != nil {
		if err := db.Rdb.Set(ctx, logKey, data, 0).Err(); err != nil {
			return utils.Internal(c, "failed to store log")
		}

		userZsetKey := fmt.Sprintf("miler_periodic_logs:%d", log.UserID)
		db.Rdb.ZAdd(ctx, userZsetKey, redis.Z{Score: float64(timestamp), Member: logKey})
		db.Rdb.ZAdd(ctx, "miler_periodic_logs_all", redis.Z{Score: float64(timestamp), Member: logKey})
	}

	return utils.Message(c, "miler periodic log stored successfully")
}

func GetMilerPeriodicLogs(c *fiber.Ctx) error {
	ctx := context.Background()

	if db.Rdb == nil {
		return utils.Internal(c, "cache service unavailable")
	}

	userID := c.Query("userid")

	var keys []string
	var err error

	if userID != "" {
		zsetKey := fmt.Sprintf("miler_periodic_logs:%s", userID)
		keys, err = db.Rdb.ZRevRange(ctx, zsetKey, 0, 0).Result()
	} else {
		keys, err = db.Rdb.ZRevRange(ctx, "miler_periodic_logs_all", 0, 0).Result()
	}

	if err != nil {
		return utils.Internal(c, "failed to fetch logs")
	}

	if len(keys) == 0 {
		return utils.List(c, []interface{}{}, 0)
	}

	val, err := db.Rdb.Get(ctx, keys[0]).Result()
	if err != nil {
		return utils.Internal(c, "failed to retrieve log data")
	}

	var log map[string]interface{}
	json.Unmarshal([]byte(val), &log)

	return utils.OK(c, log)
}

func CreateMilerStatus(c *fiber.Ctx) error {
	ctx := context.Background()

	var status models.MilerStatus
	if err := c.BodyParser(&status); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	if status.UserID == 0 || status.Status == "" {
		return utils.BadRequest(c, "userid and status are required")
	}

	key := fmt.Sprintf("miler_status:%d", status.UserID)
	data, _ := json.Marshal(status)

	if db.Rdb != nil {
		if err := db.Rdb.Set(ctx, key, data, 0).Err(); err != nil {
			return utils.Internal(c, "failed to store status")
		}

		db.Rdb.ZAdd(ctx, "miler_status_all", redis.Z{
			Score:  float64(time.Now().Unix()),
			Member: key,
		})
	}

	return utils.Message(c, "miler status updated successfully")
}

func GetMilerStatus(c *fiber.Ctx) error {
	ctx := context.Background()

	if db.Rdb == nil {
		return utils.Internal(c, "cache service unavailable")
	}

	userIDStr := c.Query("userid")

	if userIDStr != "" {
		key := fmt.Sprintf("miler_status:%s", userIDStr)

		val, err := db.Rdb.Get(ctx, key).Result()
		if err != nil {
			return utils.NotFound(c, "status not found for this miler")
		}

		var data map[string]interface{}
		json.Unmarshal([]byte(val), &data)

		return utils.OK(c, data)
	}

	pageStr := c.Query("page")
	pageSizeStr := c.Query("pagesize")

	page, _ := strconv.Atoi(pageStr)
	pageSize, _ := strconv.Atoi(pageSizeStr)

	var start, end int64
	if page > 0 && pageSize > 0 {
		offset := (page - 1) * pageSize
		start = int64(offset)
		end = int64(offset + pageSize - 1)
	} else {
		start = 0
		end = -1
	}

	keys, err := db.Rdb.ZRevRange(ctx, "miler_status_all", start, end).Result()
	if err != nil {
		return utils.Internal(c, "failed to fetch statuses")
	}

	if len(keys) == 0 {
		return utils.List(c, []interface{}{}, 0)
	}

	values, err := db.Rdb.MGet(ctx, keys...).Result()
	if err != nil {
		return utils.Internal(c, "failed to retrieve status data")
	}

	var result []map[string]interface{}
	for _, val := range values {
		if val == nil {
			continue
		}
		var item map[string]interface{}
		json.Unmarshal([]byte(val.(string)), &item)
		result = append(result, item)
	}

	return utils.List(c, result, int64(len(result)))
}

func PublishConsignmentLogs(c *fiber.Ctx) error {
	var input []models.ConsignmentLog
	if err := c.BodyParser(&input); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	if len(input) == 0 {
		return utils.BadRequest(c, "at least one log entry is required")
	}

	if db.Rdb == nil {
		return utils.Internal(c, "cache service unavailable")
	}

	pipe := db.Rdb.TxPipeline()
	tx := db.DB.Begin()

	for _, item := range input {
		logTime, err := time.Parse("2006-01-02 15:04:05", item.LogDate)
		if err != nil {
			logTime = time.Now()
		}

		ts := logTime.Unix()
		logKey := "Consignmentlogs:" + strconv.Itoa(item.ConsignmentID)
		userIndexKey := "user:consignmentlogs:" + strconv.Itoa(item.UserID)

		jsonData, _ := json.Marshal(item)

		pipe.RPush(db.Ctx, logKey, jsonData)
		pipe.ZAdd(db.Ctx, userIndexKey, redis.Z{
			Score:  float64(ts),
			Member: item.ConsignmentID,
		})

		history := models.ConsignmentHistory{
			Consignmentid: item.ConsignmentID,
			Userid:        &item.UserID,
			Eventstatus:   item.Status,
			Remarks:       fmt.Sprintf("GPS Update: Lat %s, Lon %s. Speed %s. Remarks: %s", item.Latitude, item.Longitude, item.Speed, item.Remarks),
			Createdat:     logTime,
		}
		if err := tx.Create(&history).Error; err != nil {
			tx.Rollback()
			return utils.Internal(c, "failed to persist consignment log")
		}
	}

	if _, err := pipe.Exec(db.Ctx); err != nil {
		tx.Rollback()
		return utils.Internal(c, "failed to publish logs to cache")
	}

	tx.Commit()
	return utils.Message(c, "consignment logs published successfully")
}

func GetConsignmentLogs(c *fiber.Ctx) error {
	consignmentID, err := strconv.Atoi(c.Params("consignmentid"))
	if err != nil {
		return utils.BadRequest(c, "invalid consignment ID")
	}

	if db.Rdb == nil {
		return utils.Internal(c, "cache service unavailable")
	}

	logKey := "Consignmentlogs:" + strconv.Itoa(consignmentID)
	redisList, err := db.Rdb.LRange(db.Ctx, logKey, 0, -1).Result()

	if err == nil && len(redisList) > 0 {
		var logs []map[string]interface{}
		for _, raw := range redisList {
			var m map[string]interface{}
			json.Unmarshal([]byte(raw), &m)
			logs = append(logs, m)
		}
		return utils.List(c, logs, int64(len(logs)))
	}

	var history []models.ConsignmentHistory
	if err := db.DB.Where("consignmentid = ?", consignmentID).Order("createdat ASC").Find(&history).Error; err != nil {
		return utils.Internal(c, "failed to fetch consignment logs")
	}

	return utils.List(c, history, int64(len(history)))
}

func GetUserConsignmentLogs(c *fiber.Ctx) error {
	userID, err := strconv.Atoi(c.Params("userid"))
	if err != nil {
		return utils.BadRequest(c, "invalid user ID")
	}

	if db.Rdb == nil {
		return utils.Internal(c, "cache service unavailable")
	}

	userIndexKey := "user:consignmentlogs:" + strconv.Itoa(userID)
	members, err := db.Rdb.ZRevRange(db.Ctx, userIndexKey, 0, -1).Result()
	if err != nil {
		return utils.Internal(c, "failed to fetch consignment log index")
	}

	var logs []map[string]interface{}
	for _, consignmentIDStr := range members {
		logKey := "Consignmentlogs:" + consignmentIDStr
		rawList, err := db.Rdb.LRange(db.Ctx, logKey, -1, -1).Result()
		if err == nil && len(rawList) > 0 {
			var m map[string]interface{}
			json.Unmarshal([]byte(rawList[0]), &m)
			logs = append(logs, m)
		}
	}

	return utils.List(c, logs, int64(len(logs)))
}

func SaveMilerDeviceToken(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var req struct {
		DeviceToken string `json:"device_token"`
	}
	if err := c.BodyParser(&req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if req.DeviceToken == "" {
		return utils.BadRequest(c, "device_token is required")
	}

	if err := db.DB.Model(&models.MilerProfile{}).
		Where("userid = ?", milerUserID).
		Update("device_token", req.DeviceToken).Error; err != nil {
		return utils.Internal(c, "failed to save device token")
	}

	return utils.Message(c, "device token saved")
}
