package controllers

import (
	"context"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"
	"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"
)

// Helper to generate tripsheet number
func generateTripsheetNo() string {
	b := make([]byte, 4)
	rand.Read(b)
	return fmt.Sprintf("DM-TS-%X-%d", b, time.Now().Unix()%100000)
}

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

		if req.Email == "" || req.Password == "" {
			return utils.BadRequest(c, "email and password are required")
		}

		var auth models.DoormileAuth
		if err := db.DB.Where("email = ?", req.Email).First(&auth).Error; err != nil {
			return utils.Unauthorized(c, "incorrect email or password")
		}

		if auth.Role != "admin" && auth.Role != "manager" && auth.Role != "executive" {
			return utils.Forbidden(c, "access restricted to admin console users")
		}

		if !utils.CheckPasswordHash(req.Password, auth.PasswordHash) {
			return utils.Unauthorized(c, "incorrect email or password")
		}

		roleId := 3
		if auth.Role == "admin" {
			roleId = 1
		} else if auth.Role == "executive" {
			roleId = 4
		}

		var appUser models.AppUser
		db.DB.Where("email = ?", req.Email).First(&appUser)
		userName := "Admin"
		if appUser.Authname != "" {
			userName = appUser.Authname
		}

		// Important: use appUser.Userid instead of auth.ID to ensure consistent IDs across the system
		token, err := utils.GenerateToken(int(appUser.Userid), auth.Email, roleId, 0, 1, cfg.JWTSecret)
		if err != nil {
			return utils.Internal(c, "failed to generate token")
		}

		return c.JSON(fiber.Map{
			"success": true,
			"token":   token,
			"user": fiber.Map{
				"id":    appUser.Userid,
				"name":  userName,
				"email": auth.Email,
				"role":  auth.Role,
			},
		})
	}
}

func GetAdminDashboard(c *fiber.Ctx) error {
	var totalTenants int64
	var totalCustomers int64
	var totalMilers int64
	var totalBookings int64
	var totalConsignments int64
	var openExceptions int64

	db.DB.Model(&models.Tenant{}).Count(&totalTenants)
	db.DB.Model(&models.AppCustomer{}).Count(&totalCustomers)
	db.DB.Model(&models.AppUser{}).Where("roleid = 5").Count(&totalMilers)
	db.DB.Model(&models.PickupBooking{}).Count(&totalBookings)
	db.DB.Model(&models.Consignment{}).Count(&totalConsignments)
	db.DB.Model(&models.ConsignmentException{}).Where("status = ?", "Open").Count(&openExceptions)

	return utils.OK(c, fiber.Map{
		"tenants":      totalTenants,
		"customers":    totalCustomers,
		"milers":       totalMilers,
		"bookings":     totalBookings,
		"consignments": totalConsignments,
		"exceptions":   openExceptions,
	})
}

// --------------------
// APP USERS MANAGEMENT
// --------------------

func GetAppUsers(c *fiber.Ctx) error {
	var users []models.AppUser
	// Exclude Milers (Roleid = 5) from the CRM user list
	if err := db.DB.Where("roleid != ?", 5).Find(&users).Error; err != nil {
		return utils.Internal(c, "failed to fetch users")
	}

	response := make([]fiber.Map, 0, len(users))
	for _, u := range users {
		roleName := "unknown"
		if u.Roleid == 1 {
			roleName = "admin"
		} else if u.Roleid == 3 {
			roleName = "manager"
		} else if u.Roleid == 4 {
			roleName = "rep"
		} else if u.Roleid == 5 {
			roleName = "miler"
		}

		response = append(response, fiber.Map{
			"id":         u.Userid,
			"first_name": u.Authname,
			"email":      u.Email,
			"phone":      u.Contactno,
			"role":       roleName,
			"status":     u.Status,
		})
	}
	return utils.List(c, response, int64(len(response)))
}

func CreateAppUser(c *fiber.Ctx) error {
	type UserRequest struct {
		FirstName string `json:"first_name"`
		Email     string `json:"email"`
		Phone     string `json:"phone"`
		Role      string `json:"role"`
		Password  string `json:"password"`
	}
	req := new(UserRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	roleId := 4
	if req.Role == "admin" {
		roleId = 1
	} else if req.Role == "manager" {
		roleId = 3
	} else if req.Role == "rep" {
		roleId = 4
	}

	passHash, _ := utils.HashPassword(req.Password)
	if passHash == "" {
		passHash, _ = utils.HashPassword("defaultPassword123")
	}

	user := models.AppUser{
		Authname:      req.FirstName,
		Email:         req.Email,
		Contactno:     req.Phone,
		Password:      passHash,
		Roleid:        roleId,
		Status:        "Active",
		Applocationid: 1,
	}

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

	return utils.Created(c, fiber.Map{
		"id":         user.Userid,
		"first_name": user.Authname,
		"email":      user.Email,
		"phone":      user.Contactno,
		"role":       req.Role,
	})
}

func UpdateAppUser(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var user models.AppUser
	if err := db.DB.First(&user, id).Error; err != nil {
		return utils.NotFound(c, "user not found")
	}

	type UserRequest struct {
		FirstName string `json:"first_name"`
		Email     string `json:"email"`
		Phone     string `json:"phone"`
		Role      string `json:"role"`
	}
	req := new(UserRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	if req.FirstName != "" {
		user.Authname = req.FirstName
	}
	if req.Email != "" {
		user.Email = req.Email
	}
	if req.Phone != "" {
		user.Contactno = req.Phone
	}
	if req.Role != "" {
		if req.Role == "admin" {
			user.Roleid = 1
		} else if req.Role == "manager" {
			user.Roleid = 3
		} else if req.Role == "rep" {
			user.Roleid = 4
		}
	}
	user.Updatedat = time.Now()

	if err := db.DB.Save(&user).Error; err != nil {
		return utils.Internal(c, "failed to update user")
	}
	return utils.OK(c, fiber.Map{
		"id":         user.Userid,
		"first_name": user.Authname,
		"email":      user.Email,
		"phone":      user.Contactno,
		"role":       req.Role,
	})
}

func DeleteAppUser(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	if err := db.DB.Delete(&models.AppUser{}, id).Error; err != nil {
		return utils.Internal(c, "failed to delete user")
	}
	return utils.Message(c, "user deleted successfully")
}

// --------------------
// TENANT MANAGEMENT
// --------------------

func GetTenants(c *fiber.Ctx) error {
	var tenants []models.Tenant
	if err := db.DB.Find(&tenants).Error; err != nil {
		return utils.Internal(c, "failed to fetch tenants")
	}
	return utils.List(c, tenants, int64(len(tenants)))
}

func CreateTenant(c *fiber.Ctx) error {
	req := new(dto.TenantCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	tenant := models.Tenant{
		Tenantname:     req.Tenantname,
		Primaryemail:   req.Primaryemail,
		Primarycontact: req.Primarycontact,
		Status:         req.Status,
	}
	if tenant.Status == "" {
		tenant.Status = "Active"
	}

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

func GetTenantDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var tenant models.Tenant
	if err := db.DB.First(&tenant, id).Error; err != nil {
		return utils.NotFound(c, "tenant not found")
	}
	return utils.OK(c, tenant)
}

func UpdateTenant(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var tenant models.Tenant
	if err := db.DB.First(&tenant, id).Error; err != nil {
		return utils.NotFound(c, "tenant not found")
	}

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

	if req.Tenantname != "" {
		tenant.Tenantname = req.Tenantname
	}
	if req.Primaryemail != "" {
		tenant.Primaryemail = req.Primaryemail
	}
	if req.Primarycontact != "" {
		tenant.Primarycontact = req.Primarycontact
	}
	if req.Status != "" {
		tenant.Status = req.Status
	}
	tenant.Updatedat = time.Now()

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

func DeleteTenant(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var tenant models.Tenant
	if err := db.DB.First(&tenant, id).Error; err != nil {
		return utils.NotFound(c, "tenant not found")
	}

	if err := db.DB.Delete(&tenant).Error; err != nil {
		return utils.Internal(c, "failed to delete tenant")
	}
	return utils.Message(c, "tenant deleted successfully")
}

func GetTenantLocations(c *fiber.Ctx) error {
	tenantID, _ := strconv.Atoi(c.Params("id"))
	var locations []models.TenantLocation
	if err := db.DB.Where("tenantid = ?", tenantID).Find(&locations).Error; err != nil {
		return utils.Internal(c, "failed to fetch locations")
	}
	return utils.List(c, locations, int64(len(locations)))
}

func CreateTenantLocation(c *fiber.Ctx) error {
	tenantID, _ := strconv.Atoi(c.Params("id"))
	req := new(dto.TenantLocationCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	location := models.TenantLocation{
		Tenantid:  tenantID,
		Address:   req.Address,
		City:      req.City,
		State:     req.State,
		Pincode:   req.Pincode,
		Latitude:  req.Latitude,
		Longitude: req.Longitude,
		Isprimary: req.Isprimary,
		Status:    req.Status,
	}
	if location.Status == "" {
		location.Status = "Active"
	}

	if req.Isprimary {
		db.DB.Model(&models.TenantLocation{}).Where("tenantid = ?", tenantID).Update("isprimary", false)
	}

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

func UpdateTenantLocation(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var location models.TenantLocation
	if err := db.DB.First(&location, id).Error; err != nil {
		return utils.NotFound(c, "tenant location not found")
	}

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

	if req.Address != "" {
		location.Address = req.Address
	}
	if req.City != "" {
		location.City = req.City
	}
	if req.State != "" {
		location.State = req.State
	}
	if req.Pincode != "" {
		location.Pincode = req.Pincode
	}
	if req.Latitude != 0 {
		location.Latitude = req.Latitude
	}
	if req.Longitude != 0 {
		location.Longitude = req.Longitude
	}
	location.Isprimary = req.Isprimary
	if req.Status != "" {
		location.Status = req.Status
	}
	location.Updatedat = time.Now()

	if req.Isprimary {
		db.DB.Model(&models.TenantLocation{}).Where("tenantid = ?", location.Tenantid).Update("isprimary", false)
	}

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

// --------------------
// TENANT CUSTOMERS
// --------------------

func GetTenantCustomers(c *fiber.Ctx) error {
	var customers []models.Customer
	if err := db.DB.Find(&customers).Error; err != nil {
		return utils.Internal(c, "failed to fetch customers")
	}
	return utils.List(c, customers, int64(len(customers)))
}

// --------------------
// B2C APP CUSTOMERS
// --------------------

func GetAdminCustomers(c *fiber.Ctx) error {
	pageno := max(1, c.QueryInt("pageno", 1))
	pagesize := min(100, max(1, c.QueryInt("pagesize", 20)))
	offset := (pageno - 1) * pagesize
	keyword := c.Query("keyword")

	query := db.DB.Model(&models.AppCustomer{})
	if keyword != "" {
		like := "%" + keyword + "%"
		query = query.Where("firstname ILIKE ? OR lastname ILIKE ? OR phone ILIKE ?", like, like, like)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return utils.Internal(c, "failed to count customers")
	}

	var customers []models.AppCustomer
	if err := query.Offset(offset).Limit(pagesize).Find(&customers).Error; err != nil {
		return utils.Internal(c, "failed to fetch customers")
	}

	ids := make([]int, 0, len(customers))
	for _, cust := range customers {
		ids = append(ids, cust.Appcustomerid)
	}

	bookingCounts := make(map[int]int64, len(ids))
	if len(ids) > 0 {
		type countRow struct {
			Appcustomerid int
			Cnt           int64
		}
		var rows []countRow
		if err := db.DB.Model(&models.PickupBooking{}).
			Select("appcustomerid, count(*) as cnt").
			Where("appcustomerid IN ?", ids).
			Group("appcustomerid").
			Scan(&rows).Error; err == nil {
			for _, r := range rows {
				bookingCounts[r.Appcustomerid] = r.Cnt
			}
		}
	}

	data := make([]fiber.Map, 0, len(customers))
	for _, cust := range customers {
		data = append(data, fiber.Map{
			"appcustomerid": cust.Appcustomerid,
			"name":          strings.TrimSpace(cust.Firstname + " " + cust.Lastname),
			"phone":         cust.Phone,
			"email":         cust.Email,
			"createdat":     cust.Createdat,
			"totalbookings": bookingCounts[cust.Appcustomerid],
		})
	}

	pages := int(math.Ceil(float64(total) / float64(pagesize)))

	return c.JSON(fiber.Map{
		"success":  true,
		"data":     data,
		"total":    total,
		"pageno":   pageno,
		"pagesize": pagesize,
		"pages":    pages,
	})
}

func UpdateAdminCustomer(c *fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return utils.BadRequest(c, "invalid customer id")
	}

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

	req := new(struct {
		Name  string `json:"name"`
		Phone string `json:"phone"`
		Email string `json:"email"`
	})
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	if req.Phone != "" {
		if len(req.Phone) != 10 || strings.IndexFunc(req.Phone, func(r rune) bool { return r < '0' || r > '9' }) != -1 {
			return utils.BadRequest(c, "phone must be 10 digits")
		}
		customer.Phone = req.Phone
	}

	if req.Name != "" {
		name := strings.TrimSpace(req.Name)
		parts := strings.SplitN(name, " ", 2)
		customer.Firstname = parts[0]
		if len(parts) > 1 {
			customer.Lastname = parts[1]
		} else {
			customer.Lastname = ""
		}
	}

	if req.Email != "" {
		customer.Email = req.Email
	}

	customer.Updatedat = time.Now()

	if err := db.DB.Model(&models.AppCustomer{}).Where("appcustomerid = ?", customer.Appcustomerid).
		Updates(map[string]interface{}{
			"firstname": customer.Firstname,
			"lastname":  customer.Lastname,
			"phone":     customer.Phone,
			"email":     customer.Email,
			"updatedat": customer.Updatedat,
		}).Error; err != nil {
		return utils.Internal(c, "failed to update customer")
	}

	return c.JSON(fiber.Map{
		"success": true,
		"data": fiber.Map{
			"appcustomerid": customer.Appcustomerid,
			"name":          strings.TrimSpace(customer.Firstname + " " + customer.Lastname),
			"phone":         customer.Phone,
			"email":         customer.Email,
		},
	})
}

func CreateTenantCustomer(c *fiber.Ctx) error {
	req := new(dto.TenantCustomerCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	customer := models.Customer{
		Firstname: req.Firstname,
		Lastname:  req.Lastname,
		Contactno: req.Phone,
		Email:     req.Email,
		Status:    1,
		Createdat: time.Now(),
		Updatedat: time.Now(),
	}

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

func GetTenantCustomerDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var customer models.Customer
	if err := db.DB.First(&customer, id).Error; err != nil {
		return utils.NotFound(c, "customer not found")
	}
	return utils.OK(c, customer)
}

func UpdateTenantCustomer(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var customer models.Customer
	if err := db.DB.First(&customer, id).Error; err != nil {
		return utils.NotFound(c, "customer not found")
	}

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

	if req.Firstname != "" {
		customer.Firstname = req.Firstname
	}
	if req.Lastname != "" {
		customer.Lastname = req.Lastname
	}
	if req.Phone != "" {
		customer.Contactno = req.Phone
	}
	if req.Email != "" {
		customer.Email = req.Email
	}
	customer.Updatedat = time.Now()

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

func DeleteTenantCustomer(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var customer models.Customer
	if err := db.DB.First(&customer, id).Error; err != nil {
		return utils.NotFound(c, "customer not found")
	}

	if err := db.DB.Delete(&customer).Error; err != nil {
		return utils.Internal(c, "failed to delete customer")
	}
	return utils.Message(c, "customer deleted successfully")
}

// --------------------
// HUBS CRUD
// --------------------

func GetHubs(c *fiber.Ctx) error {
	var hubs []models.Hub
	query := db.DB.Where("deletedat IS NULL")

	if appLocationID := c.Query("applocationid"); appLocationID != "" {
		query = query.Where("applocationid = ?", appLocationID)
	}
	if status := c.Query("status"); status != "" {
		query = query.Where("status = ?", status)
	}
	if hubType := c.Query("hubtype"); hubType != "" {
		query = query.Where("hubtype = ?", hubType)
	}

	if err := query.Find(&hubs).Error; err != nil {
		return utils.Internal(c, "failed to fetch hubs")
	}
	return utils.List(c, hubs, int64(len(hubs)))
}

func CreateHub(c *fiber.Ctx) error {
	req := new(dto.HubCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	hub := models.Hub{
		Hubname:       req.Hubname,
		Hubtype:       req.Hubtype,
		Applocationid: req.Applocationid,
		Contactno:     req.Contactno,
		Address:       req.Address,
		Latitude:      req.Latitude,
		Longitude:     req.Longitude,
		Pincode:       req.Pincode,
		Status:        req.Status,
	}
	if hub.Status == "" {
		hub.Status = "Active"
	}

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

func GetHubDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var hub models.Hub
	if err := db.DB.Where("hubid = ? AND deletedat IS NULL", id).First(&hub).Error; err != nil {
		return utils.NotFound(c, "hub not found")
	}
	return utils.OK(c, hub)
}

func UpdateHub(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var hub models.Hub
	if err := db.DB.Where("hubid = ? AND deletedat IS NULL", id).First(&hub).Error; err != nil {
		return utils.NotFound(c, "hub not found")
	}

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

	if req.Hubname != "" {
		hub.Hubname = req.Hubname
	}
	if req.Hubtype != "" {
		hub.Hubtype = req.Hubtype
	}
	if req.Applocationid != 0 {
		hub.Applocationid = req.Applocationid
	}
	if req.Address != "" {
		hub.Address = req.Address
	}
	if req.Contactno != "" {
		hub.Contactno = req.Contactno
	}
	if req.Latitude != 0 {
		hub.Latitude = req.Latitude
	}
	if req.Longitude != 0 {
		hub.Longitude = req.Longitude
	}
	if req.Pincode != "" {
		hub.Pincode = req.Pincode
	}
	if req.Status != "" {
		hub.Status = req.Status
	}
	hub.Updatedat = time.Now()

	db.DB.Save(&hub)
	return utils.OK(c, hub)
}

func DeleteHub(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var hub models.Hub
	if err := db.DB.Where("hubid = ? AND deletedat IS NULL", id).First(&hub).Error; err != nil {
		return utils.NotFound(c, "hub not found")
	}

	now := time.Now()
	hub.Deletedat = &now
	db.DB.Save(&hub)
	return utils.Message(c, "hub deleted successfully")
}

// --------------------
// VEHICLES CRUD
// --------------------

func GetVehicles(c *fiber.Ctx) error {
	var vehicles []models.Vehicle
	if err := db.DB.Where("deletedat IS NULL").Find(&vehicles).Error; err != nil {
		return utils.Internal(c, "failed to fetch vehicles")
	}
	return utils.List(c, vehicles, int64(len(vehicles)))
}

func CreateVehicle(c *fiber.Ctx) error {
	req := new(dto.VehicleCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	vehicle := models.Vehicle{
		Vehicleno:         req.Vehicleno,
		Vehicletype:       req.Vehicletype,
		Maxweight:         req.Maxweight,
		Maxvolume:         req.Maxvolume,
		Partnerid:         req.Partnerid,
		Batterypercentage: req.Batterypercentage,
		Status:            req.Status,
	}
	if vehicle.Status == "" {
		vehicle.Status = "Available"
	}

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

func GetVehicleDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var vehicle models.Vehicle
	if err := db.DB.Where("vehicleid = ? AND deletedat IS NULL", id).First(&vehicle).Error; err != nil {
		return utils.NotFound(c, "vehicle not found")
	}
	return utils.OK(c, vehicle)
}

func UpdateVehicle(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var vehicle models.Vehicle
	if err := db.DB.Where("vehicleid = ? AND deletedat IS NULL", id).First(&vehicle).Error; err != nil {
		return utils.NotFound(c, "vehicle not found")
	}

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

	if req.Vehicleno != "" {
		vehicle.Vehicleno = req.Vehicleno
	}
	if req.Vehicletype != "" {
		vehicle.Vehicletype = req.Vehicletype
	}
	if req.Maxweight != 0 {
		vehicle.Maxweight = req.Maxweight
	}
	if req.Maxvolume != 0 {
		vehicle.Maxvolume = req.Maxvolume
	}
	vehicle.Partnerid = req.Partnerid
	if req.Batterypercentage != 0 {
		vehicle.Batterypercentage = req.Batterypercentage
	}
	if req.Status != "" {
		vehicle.Status = req.Status
	}
	vehicle.Updatedat = time.Now()

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

func DeleteVehicle(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var vehicle models.Vehicle
	if err := db.DB.Where("vehicleid = ? AND deletedat IS NULL", id).First(&vehicle).Error; err != nil {
		return utils.NotFound(c, "vehicle not found")
	}

	now := time.Now()
	vehicle.Deletedat = &now
	db.DB.Save(&vehicle)
	return utils.Message(c, "vehicle deleted successfully")
}

// --------------------
// MILERS MANAGEMENT
// --------------------

func GetMilers(c *fiber.Ctx) error {
	var profiles []models.MilerProfile
	query := db.DB
	if appLocStr := c.Query("applocationid"); appLocStr != "" {
		query = query.Where("applocationid = ?", appLocStr)
	}
	if err := query.Find(&profiles).Error; err != nil {
		return utils.Internal(c, "failed to fetch milers")
	}
	return utils.List(c, profiles, int64(len(profiles)))
}

func CreateMiler(c *fiber.Ctx) error {
	req := new(dto.MilerCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	passHash, _ := utils.HashPassword(req.Password)

	tx := db.DB.Begin()

	appLocID := req.Applocationid
	if appLocID == 0 {
		appLocID = 1
	}

	user := models.AppUser{
		Authname:      req.Authname,
		Email:         req.Email,
		Contactno:     req.Contactno,
		Password:      passHash,
		Roleid:        5, // Miler
		Status:        "Active",
		Applocationid: appLocID,
	}

	if err := tx.Create(&user).Error; err != nil {
		tx.Rollback()
		return utils.Internal(c, "failed to create miler account")
	}

	profile := models.MilerProfile{
		Userid:             user.Userid,
		Displayname:        req.Displayname,
		Phone:              req.Contactno,
		Defaultvehicletype: req.Defaultvehicletype,
		Availabilitystatus: constants.MilerOffline,
		Rating:             5.00,
		Applocationid:      appLocID,
	}

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

	tx.Commit()
	return utils.Created(c, profile)
}

func GetMilerDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var profile models.MilerProfile
	if err := db.DB.Where("milerprofileid = ?", id).First(&profile).Error; err != nil {
		return utils.NotFound(c, "miler not found")
	}
	return utils.OK(c, profile)
}

func UpdateMiler(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var profile models.MilerProfile
	if err := db.DB.Where("milerprofileid = ?", id).First(&profile).Error; err != nil {
		return utils.NotFound(c, "miler not found")
	}

	type MilerUpdate struct {
		Displayname        string `json:"displayname"`
		Defaultvehicletype string `json:"defaultvehicletype"`
		Hubid              *int   `json:"hubid"`
	}

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

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

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

func BlockMiler(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

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

	profile.Availabilitystatus = constants.MilerBlocked
	profile.Updatedat = time.Now()
	tx.Save(&profile)

	tx.Model(&models.AppUser{}).Where("userid = ?", profile.Userid).Update("status", "Blocked")

	tx.Commit()
	return utils.Message(c, "miler blocked successfully")
}

func AssignMilerVehicle(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

	type VehicleAssign struct {
		Vehicleid int `json:"vehicleid"`
	}
	req := new(VehicleAssign)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

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

	profile.Vehicleid = &req.Vehicleid
	profile.Updatedat = time.Now()
	db.DB.Save(&profile)

	return utils.OK(c, profile)
}

// --------------------
// BOOKINGS MANAGEMENT
// --------------------

func GetAdminBookings(c *fiber.Ctx) error {
	pageno := max(1, c.QueryInt("pageno", 1))
	pagesize := min(100, max(1, c.QueryInt("pagesize", 20)))
	offset := (pageno - 1) * pagesize

	var total int64
	if err := db.DB.Model(&models.PickupBooking{}).Count(&total).Error; err != nil {
		return utils.Internal(c, "failed to count bookings")
	}

	var bookings []models.PickupBooking
	if err := db.DB.Preload("Parcels").Preload("ServiceOptions").
		Offset(offset).Limit(pagesize).Find(&bookings).Error; err != nil {
		return utils.Internal(c, "failed to fetch bookings")
	}

	pages := int(math.Ceil(float64(total) / float64(pagesize)))

	return c.JSON(fiber.Map{
		"success":  true,
		"data":     bookings,
		"total":    total,
		"pageno":   pageno,
		"pagesize": pagesize,
		"pages":    pages,
	})
}

func CreateCRMBooking(c *fiber.Ctx) error {
	type AdminBookingRequest struct {
		Appcustomerid       int                  `json:"appcustomerid"`
		CustomerPhone       string               `json:"customer_phone"`
		CustomerName        string               `json:"customer_name"`
		Pickupaddress       string               `json:"pickupaddress"`
		Pickuppincode       string               `json:"pickuppincode"`
		Pickuplatitude      float64              `json:"pickuplatitude"`
		Pickuplongitude     float64              `json:"pickuplongitude"`
		Deliveryaddress     string               `json:"deliveryaddress"`
		Deliverypincode     string               `json:"deliverypincode"`
		Deliverycity        string               `json:"deliverycity"`
		Deliverylatitude    float64              `json:"deliverylatitude"`
		Deliverylongitude   float64              `json:"deliverylongitude"`
		Providercompany     string               `json:"providercompany"`
		Providerlocation    string               `json:"providerlocation"`
		Notes               string               `json:"notes"`
		ServiceOption       string               `json:"service_option"`
		Finalprice          float64              `json:"finalprice"`
		Insuranceamount     float64              `json:"insuranceamount"`
		Preferredpickupfrom *time.Time           `json:"preferredpickupfrom"`
		Preferredpickupto   *time.Time           `json:"preferredpickupto"`
		Parcels             []dto.ParcelRequest  `json:"parcels"`
	}

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

	if req.Pickupaddress == "" || req.Pickuppincode == "" {
		return utils.BadRequest(c, "pickup address and pincode are required")
	}

	if len(req.Parcels) == 0 {
		return utils.BadRequest(c, "at least one parcel is required")
	}

	tx := db.DB.Begin()

	customerID := req.Appcustomerid
	if customerID == 0 && req.CustomerPhone != "" {
		// Try to find customer by phone
		var customer models.AppCustomer
		if err := tx.Where("phone = ?", req.CustomerPhone).First(&customer).Error; err == nil {
			customerID = customer.Appcustomerid
		} else {
			// Create dummy customer
			newCustomer := models.AppCustomer{
				Firstname: req.CustomerName,
				Phone:     req.CustomerPhone,
				Status:    "Active",
				Configid:  1001,
			}
			if req.CustomerName == "" {
				newCustomer.Firstname = "Guest"
			}
			if err := tx.Create(&newCustomer).Error; err != nil {
				tx.Rollback()
				return utils.Internal(c, "failed to create customer record")
			}
			customerID = newCustomer.Appcustomerid
		}
	}

	booking := models.PickupBooking{
		Bookingno:           generateBookingNo(),
		Appcustomerid:       customerID,
		Pickupaddress:       req.Pickupaddress,
		Pickuppincode:       req.Pickuppincode,
		Pickuplatitude:      req.Pickuplatitude,
		Pickuplongitude:     req.Pickuplongitude,
		Deliveryaddress:     req.Deliveryaddress,
		Deliverypincode:     req.Deliverypincode,
		Deliverycity:        req.Deliverycity,
		Deliverylatitude:    req.Deliverylatitude,
		Deliverylongitude:   req.Deliverylongitude,
		Bookingsource:       "CRM_Console",
		Providercompany:     req.Providercompany,
		Providerlocation:    req.Providerlocation,
		Notes:               req.Notes,
		Status:              constants.BookingPendingPickup,
		Preferredpickupfrom: req.Preferredpickupfrom,
		Preferredpickupto:   req.Preferredpickupto,
	}

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

	var totalWeight float64
	var totalVolume float64
	var requiresLargeVehicle bool
	for _, p := range req.Parcels {
		volumetric := calculateVolumetricWeight(p.Length, p.Width, p.Height)
		totalWeight += math.Max(p.Weight, volumetric)
		totalVolume += p.Length * p.Width * p.Height

		parcel := models.BookingParcel{
			Bookingid:            booking.Bookingid,
			Itemcategory:         p.Itemcategory,
			Itemdescription:      p.Itemdescription,
			Declaredvalue:        p.Declaredvalue,
			Weight:               p.Weight,
			Length:               p.Length,
			Width:                p.Width,
			Height:               p.Height,
			Isfragile:            p.Isfragile,
			Needsinsurance:       p.Needsinsurance,
			Requireslargevehicle: p.Requireslargevehicle,
		}

		if p.Requireslargevehicle {
			requiresLargeVehicle = true
		}

		if p.Needsinsurance {
			parcel.Insuranceamount = p.Declaredvalue * 0.01
		}
		
		// If explicit insurance amount is provided from CRM, apply it to the first parcel
		if req.Insuranceamount > 0 && totalWeight == math.Max(p.Weight, calculateVolumetricWeight(p.Length, p.Width, p.Height)) {
			parcel.Insuranceamount = req.Insuranceamount
			parcel.Needsinsurance = true
		}

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

	var distance float64
	if booking.Deliverylatitude != 0 && booking.Deliverylongitude != 0 {
		distance = calculateDistance(booking.Pickuplatitude, booking.Pickuplongitude, booking.Deliverylatitude, booking.Deliverylongitude)
	}
	
	var pricing models.Pricing
	err := tx.Where("status = ? AND ? BETWEEN effectivefrom AND effectiveto", "Active", time.Now()).Order("priority DESC").First(&pricing).Error
	
	var estimatedPrice float64
	var pricingID *int
	if err == nil {
		pricingID = &pricing.Pricingid
		kmExtra := math.Max(0, distance-pricing.Basedistance)
		kgExtra := math.Max(0, totalWeight-pricing.Baseweight)
		estimatedPrice = pricing.Baseprice + (kmExtra * pricing.Priceperkm) + (kgExtra * pricing.Priceperkg) + pricing.Handlingcharges
	} else {
		estimatedPrice = 50.0 + (distance * 5.0) + (totalWeight * 10.0)
	}

	if req.Finalprice > 0 {
		estimatedPrice = req.Finalprice
	}

	serviceType := req.ServiceOption
	if serviceType == "" {
		serviceType = "Normal"
	}

	if serviceType == "Fast" {
		estimatedPrice *= 1.25
	} else if serviceType == "Superfast" {
		estimatedPrice *= 1.5
	}

	now := time.Now()
	estDelivery := now.Add(24 * time.Hour)
	slaDue := now.Add(36 * time.Hour)
	if serviceType == "Fast" {
		estDelivery = now.Add(12 * time.Hour)
		slaDue = now.Add(18 * time.Hour)
	} else if serviceType == "Superfast" {
		estDelivery = now.Add(6 * time.Hour)
		slaDue = now.Add(9 * time.Hour)
	}

	srvOption := models.BookingServiceOption{
		Bookingid:           booking.Bookingid,
		Servicetype:         serviceType,
		Estimatedprice:      estimatedPrice,
		Estimateddeliveryat: &estDelivery,
		Sladueat:            &slaDue,
		Pricingid:           pricingID,
	}

	if err := tx.Create(&srvOption).Error; err != nil {
		tx.Rollback()
		return utils.Internal(c, "failed to save service option")
	}

	if requiresLargeVehicle || totalWeight > 20.0 {
		reqVeh := models.BookingVehicleRequirement{
			Bookingid:           booking.Bookingid,
			Requiredvehicletype: "truck",
			Reason:              "Oversized package / heavy weight",
			Status:              "Required",
		}
		tx.Create(&reqVeh)
	}

	tx.Commit()

	go assignment.AssignCRMMiler(booking.Bookingid)

	if db.Js != nil {
		payload := map[string]interface{}{
			"booking_id":       booking.Bookingid,
			"booking_no":       booking.Bookingno,
			"customer_id":      booking.Appcustomerid,
			"pickup_address":   booking.Pickupaddress,
			"pickup_pincode":   booking.Pickuppincode,
			"status":           constants.BookingPendingPickup,
			"created_at":       time.Now().UnixMilli(),
		}
		if data, err := json.Marshal(payload); err == nil {
			db.Js.Publish("api.v1.bookings.create", data)
		}
	}

	db.DB.Preload("Parcels").Preload("ServiceOptions").First(&booking, booking.Bookingid)

	return utils.Created(c, booking)
}

func GetAdminBookingDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var booking models.PickupBooking
	if err := db.DB.Preload("Parcels").Preload("ServiceOptions").Preload("Payments").First(&booking, id).Error; err != nil {
		return utils.NotFound(c, "booking not found")
	}
	return utils.OK(c, booking)
}

func AdminAssignMiler(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

	type MilerAssign struct {
		Mileruserid int `json:"mileruserid"`
	}
	req := new(MilerAssign)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	adminUserID := c.Locals("userid").(int)

	booking, err := AssignMilerToBooking(id, req.Mileruserid, &adminUserID)
	if err != nil {
		return utils.NotFound(c, "booking not found")
	}

	return utils.OK(c, booking)
}

func AdminAssignVehicle(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

	type VehicleAssign struct {
		Vehicleid    int `json:"vehicleid"`
		Driveruserid int `json:"driveruserid"`
	}
	req := new(VehicleAssign)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	var reqVeh models.BookingVehicleRequirement
	if err := db.DB.Where("bookingid = ? AND status = ?", id, "Required").First(&reqVeh).Error; err != nil {
		return utils.NotFound(c, "no active vehicle requirement found for this booking")
	}

	reqVeh.Assignedvehicleid = &req.Vehicleid
	reqVeh.Assigneddriveruserid = &req.Driveruserid
	reqVeh.Status = "Assigned"
	reqVeh.Updatedat = time.Now()
	db.DB.Save(&reqVeh)

	return utils.OK(c, reqVeh)
}

func AdminUpdateBookingStatus(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

	type StatusUpdate struct {
		Status string `json:"status"`
	}
	req := new(StatusUpdate)
	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 booking models.PickupBooking
	if err := db.DB.First(&booking, id).Error; err != nil {
		return utils.NotFound(c, "booking not found")
	}

	booking.Status = req.Status
	booking.Updatedat = time.Now()
	db.DB.Save(&booking)

	return utils.OK(c, booking)
}

// AdminCancelBooking cancels a booking on behalf of operations, frees up the
// assigned miler (if any), and notifies downstream systems (NATS + customer push).
// A booking that has already been converted to a consignment (shipped) or is
// already cancelled cannot be cancelled again.
func AdminCancelBooking(c *fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return utils.BadRequest(c, "invalid booking ID")
	}

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

	if booking.Status == constants.BookingConvertedConsignment || booking.Status == constants.BookingCancelled {
		return utils.BadRequest(c, "cannot cancel a delivered or already cancelled booking")
	}

	booking.Status = constants.BookingCancelled
	booking.Updatedat = time.Now()
	if err := db.DB.Save(&booking).Error; err != nil {
		return utils.Internal(c, "failed to cancel booking")
	}

	if booking.Assignedmileruserid != nil {
		db.DB.Model(&models.MilerProfile{}).
			Where("userid = ?", *booking.Assignedmileruserid).
			Update("availabilitystatus", constants.MilerAvailable)
	}

	if db.Js != nil {
		payload := map[string]interface{}{
			"bookingid": booking.Bookingid,
			"reason":    "admin_cancelled",
		}
		if data, err := json.Marshal(payload); err == nil {
			if _, err := db.Js.Publish("booking.cancelled", data); err != nil {
				utils.Warn("AdminCancelBooking: NATS publish failed", "booking_id", booking.Bookingid, "error", err)
			}
		}
	}

	var customer models.AppCustomer
	if err := db.DB.Where("appcustomerid = ?", booking.Appcustomerid).First(&customer).Error; err == nil && customer.Devicetoken != "" {
		if err := notify.SendToDevice(customer.Devicetoken, "Booking Cancelled", "Your booking has been cancelled by operations", nil); err != nil {
			utils.Warn("AdminCancelBooking: failed to notify customer", "booking_id", booking.Bookingid, "error", err)
		}
	}

	return utils.Message(c, "booking cancelled")
}

// --------------------
// CONSIGNMENTS
// --------------------

func GetAdminConsignments(c *fiber.Ctx) error {
	var list []models.Consignment
	if err := db.DB.Find(&list).Error; err != nil {
		return utils.Internal(c, "failed to fetch consignments")
	}
	return utils.List(c, list, int64(len(list)))
}

func GetAdminConsignmentDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var csg models.Consignment
	if err := db.DB.First(&csg, id).Error; err != nil {
		return utils.NotFound(c, "consignment not found")
	}
	return utils.OK(c, csg)
}

func GetAdminConsignmentTracking(c *fiber.Ctx) error {
	trackingNo := c.Params("trackingno")
	var consignment models.Consignment
	if err := db.DB.Where("trackingno = ?", trackingNo).First(&consignment).Error; err != nil {
		return utils.NotFound(c, "consignment not found")
	}
	var history []models.ConsignmentHistory
	db.DB.Where("consignmentid = ?", consignment.Consignmentid).Order("createdat DESC").Find(&history)
	return utils.OK(c, fiber.Map{"consignment": consignment, "history": history})
}

func AdminUpdateConsignmentStatus(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

	type StatusUpdate struct {
		Status  string `json:"status"`
		Remarks string `json:"remarks"`
	}
	req := new(StatusUpdate)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

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

	tx := db.DB.Begin()

	var consignment models.Consignment
	if err := tx.First(&consignment, id).Error; err != nil {
		tx.Rollback()
		return utils.NotFound(c, "consignment not found")
	}

	consignment.Status = req.Status
	consignment.Updatedat = time.Now()
	tx.Save(&consignment)

	adminUserID := c.Locals("userid").(int)

	history := models.ConsignmentHistory{
		Consignmentid: consignment.Consignmentid,
		Userid:        &adminUserID,
		Eventstatus:   req.Status,
		Remarks:       req.Remarks,
	}
	tx.Create(&history)

	tx.Commit()
	return utils.OK(c, consignment)
}

// --------------------
// TRIPSHEETS (MANIFEST)
// --------------------

func GetTripsheets(c *fiber.Ctx) error {
	var list []models.Tripsheet
	if err := db.DB.Where("deletedat IS NULL").Find(&list).Error; err != nil {
		return utils.Internal(c, "failed to fetch tripsheets")
	}
	return utils.List(c, list, int64(len(list)))
}

func CreateTripsheet(c *fiber.Ctx) error {
	req := new(dto.TripsheetCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	adminUserID := c.Locals("userid").(int)

	tripsheet := models.Tripsheet{
		Tripsheetno:      generateTripsheetNo(),
		Sourcehubid:      req.Sourcehubid,
		Destinationhubid: req.Destinationhubid,
		Vehicleid:        req.Vehicleid,
		Driveruserid:     req.Driveruserid,
		Status:           constants.TripsheetDraft,
		Createdby:        adminUserID,
		Updatedby:        adminUserID,
	}

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

func GetTripsheetDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var tripsheet models.Tripsheet
	if err := db.DB.Where("tripsheetid = ? AND deletedat IS NULL", id).First(&tripsheet).Error; err != nil {
		return utils.NotFound(c, "tripsheet not found")
	}
	var items []models.TripsheetItem
	db.DB.Where("tripsheetid = ?", id).Find(&items)
	return utils.OK(c, fiber.Map{"tripsheet": tripsheet, "items": items})
}

func AddTripsheetItem(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	req := new(dto.TripsheetItemAddRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	adminUserID := c.Locals("userid").(int)

	item := models.TripsheetItem{
		Tripsheetid:   id,
		Consignmentid: req.Consignmentid,
		Scanstatus:    constants.ScanPending,
		Createdby:     adminUserID,
		Updatedby:     adminUserID,
	}

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

func DeleteTripsheetItem(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	itemID, _ := strconv.Atoi(c.Params("itemid"))

	var item models.TripsheetItem
	if err := db.DB.Where("tripsheetid = ? AND tripsheetitemid = ?", id, itemID).First(&item).Error; err != nil {
		return utils.NotFound(c, "item not found on this tripsheet")
	}

	db.DB.Delete(&item)
	return utils.Message(c, "item removed from tripsheet")
}

func DispatchTripsheet(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

	tx := db.DB.Begin()
	var tripsheet models.Tripsheet
	if err := tx.Where("tripsheetid = ?", id).First(&tripsheet).Error; err != nil {
		tx.Rollback()
		return utils.NotFound(c, "tripsheet not found")
	}

	now := time.Now()
	tripsheet.Status = constants.TripsheetDispatched
	tripsheet.Dispatchtime = &now
	tripsheet.Updatedat = now
	tx.Save(&tripsheet)

	// Fetch all loaded items
	var items []models.TripsheetItem
	tx.Where("tripsheetid = ?", id).Find(&items)

	adminUserID := c.Locals("userid").(int)

	for _, item := range items {
		// Update item scanning
		tx.Model(&item).Updates(map[string]interface{}{
			"scanstatus": constants.ScanLoaded,
			"scannedat":  &now,
		})

		// Update consignment status to In_Transit
		tx.Model(&models.Consignment{}).Where("consignmentid = ?", item.Consignmentid).Updates(map[string]interface{}{
			"status":    constants.ConsignmentInTransit,
			"updatedat": now,
		})

		// Log history
		history := models.ConsignmentHistory{
			Consignmentid: item.Consignmentid,
			Tripsheetid:   &id,
			Userid:        &adminUserID,
			Eventstatus:   constants.ConsignmentInTransit,
			Remarks:       fmt.Sprintf("Consignment dispatched on Tripsheet %s", tripsheet.Tripsheetno),
		}
		tx.Create(&history)
	}

	tx.Commit()
	return utils.OK(c, tripsheet)
}

func ArriveTripsheet(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

	tx := db.DB.Begin()
	var tripsheet models.Tripsheet
	if err := tx.Where("tripsheetid = ?", id).First(&tripsheet).Error; err != nil {
		tx.Rollback()
		return utils.NotFound(c, "tripsheet not found")
	}

	now := time.Now()
	tripsheet.Status = constants.TripsheetArrived
	tripsheet.Arrivaltime = &now
	tripsheet.Updatedat = now
	tx.Save(&tripsheet)

	// Fetch items
	var items []models.TripsheetItem
	tx.Where("tripsheetid = ?", id).Find(&items)

	adminUserID := c.Locals("userid").(int)

	for _, item := range items {
		tx.Model(&item).Updates(map[string]interface{}{
			"scanstatus": constants.ScanUnloaded,
			"scannedat":  &now,
		})

		// Update consignment status back to Inwarded_at_Hub at destination
		tx.Model(&models.Consignment{}).Where("consignmentid = ?", item.Consignmentid).Updates(map[string]interface{}{
			"status":       constants.ConsignmentInwardedAtHub,
			"currenthubid": tripsheet.Destinationhubid,
			"updatedat":    now,
		})

		// Log history
		history := models.ConsignmentHistory{
			Consignmentid: item.Consignmentid,
			Tripsheetid:   &id,
			Hubid:         &tripsheet.Destinationhubid,
			Userid:        &adminUserID,
			Eventstatus:   constants.ConsignmentInwardedAtHub,
			Remarks:       fmt.Sprintf("Consignment arrived at Hub on Tripsheet %s", tripsheet.Tripsheetno),
		}
		tx.Create(&history)
	}

	tx.Commit()
	return utils.OK(c, tripsheet)
}

// --------------------
// PRICING CRUD
// --------------------

func GetPricing(c *fiber.Ctx) error {
	var pricing []models.Pricing
	if err := db.DB.Where("deletedat IS NULL").Find(&pricing).Error; err != nil {
		return utils.Internal(c, "failed to fetch pricing schedules")
	}
	return utils.List(c, pricing, int64(len(pricing)))
}

func CreatePricing(c *fiber.Ctx) error {
	req := new(dto.PricingCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	adminUserID := c.Locals("userid").(int)

	pricing := models.Pricing{
		Tenantid:        req.Tenantid,
		Applocationid:   req.Applocationid,
		Vehicletype:     req.Vehicletype,
		Baseprice:       req.Baseprice,
		Baseweight:      req.Baseweight,
		Priceperkg:      req.Priceperkg,
		Basedistance:    req.Basedistance,
		Priceperkm:      req.Priceperkm,
		Handlingcharges: req.Handlingcharges,
		Effectivefrom:   req.Effectivefrom,
		Effectiveto:     req.Effectiveto,
		Currency:        req.Currency,
		Priority:        req.Priority,
		Status:          req.Status,
		Createdby:       adminUserID,
		Updatedby:       adminUserID,
	}
	if pricing.Currency == "" {
		pricing.Currency = "INR"
	}
	if pricing.Status == "" {
		pricing.Status = "Active"
	}

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

func UpdatePricing(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var pricing models.Pricing
	if err := db.DB.Where("pricingid = ? AND deletedat IS NULL", id).First(&pricing).Error; err != nil {
		return utils.NotFound(c, "pricing schedule not found")
	}

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

	if req.Baseprice != 0 {
		pricing.Baseprice = req.Baseprice
	}
	if req.Baseweight != 0 {
		pricing.Baseweight = req.Baseweight
	}
	if req.Priceperkg != 0 {
		pricing.Priceperkg = req.Priceperkg
	}
	if req.Basedistance != 0 {
		pricing.Basedistance = req.Basedistance
	}
	if req.Priceperkm != 0 {
		pricing.Priceperkm = req.Priceperkm
	}
	pricing.Handlingcharges = req.Handlingcharges
	if !req.Effectivefrom.IsZero() {
		pricing.Effectivefrom = req.Effectivefrom
	}
	if !req.Effectiveto.IsZero() {
		pricing.Effectiveto = req.Effectiveto
	}
	if req.Status != "" {
		pricing.Status = req.Status
	}
	pricing.Updatedat = time.Now()
	pricing.Updatedby = c.Locals("userid").(int)

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

func DeletePricing(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var pricing models.Pricing
	if err := db.DB.Where("pricingid = ? AND deletedat IS NULL", id).First(&pricing).Error; err != nil {
		return utils.NotFound(c, "pricing schedule not found")
	}

	now := time.Now()
	pricing.Deletedat = &now
	db.DB.Save(&pricing)
	return utils.Message(c, "pricing schedule deleted successfully")
}

func GetPricingQuoteSimulate(c *fiber.Ctx) error {
	req := new(dto.PricingQuoteRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	distance := calculateDistance(req.Pickuplatitude, req.Pickuplongitude, req.Deliverylatitude, req.Deliverylongitude)
	
	var totalWeight float64
	for _, p := range req.Parcels {
		volumetric := calculateVolumetricWeight(p.Length, p.Width, p.Height)
		totalWeight += math.Max(p.Weight, volumetric)
	}

	var pricing models.Pricing
	err := db.DB.Where("status = ? AND ? BETWEEN effectivefrom AND effectiveto", "Active", time.Now()).Order("priority DESC").First(&pricing).Error

	var baseQuote float64
	var pricingIDPtr *int
	if err == nil {
		pricingIDPtr = &pricing.Pricingid
		kmExtra := math.Max(0, distance-pricing.Basedistance)
		kgExtra := math.Max(0, totalWeight-pricing.Baseweight)
		baseQuote = pricing.Baseprice + (kmExtra * pricing.Priceperkm) + (kgExtra * pricing.Priceperkg) + pricing.Handlingcharges
	} else {
		// Fallback simple pricing engine
		baseQuote = 50.0 + (distance * 5.0) + (totalWeight * 10.0)
	}

	return utils.OK(c, fiber.Map{
		"distance_km":       distance,
		"chargeable_weight": totalWeight,
		"base_quote":        baseQuote,
		"pricing_id":        pricingIDPtr,
	})
}

// --------------------
// EXCEPTIONS
// --------------------

func GetExceptions(c *fiber.Ctx) error {
	var list []models.ConsignmentException
	if err := db.DB.Where("deletedat IS NULL").Find(&list).Error; err != nil {
		return utils.Internal(c, "failed to fetch exceptions")
	}
	return utils.List(c, list, int64(len(list)))
}

func CreateException(c *fiber.Ctx) error {
	req := new(dto.ExceptionCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	adminUserID := c.Locals("userid").(int)

	exception := models.ConsignmentException{
		Consignmentid:    req.Consignmentid,
		Tripsheetid:      req.Tripsheetid,
		Hubid:            req.Hubid,
		Reportedbyuserid: &adminUserID,
		Exceptiontype:    req.Exceptiontype,
		Severity:         req.Severity,
		Description:      req.Description,
		Status:           constants.ExceptionOpen,
		Createdby:        adminUserID,
		Updatedby:        adminUserID,
	}
	if exception.Severity == "" {
		exception.Severity = "Medium"
	}

	tx := db.DB.Begin()

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

	// Update consignment status to Missing or Damaged if critical exception type matches
	var cStatus string
	if req.Exceptiontype == constants.ExceptionLost {
		cStatus = constants.ConsignmentMissing
	} else if req.Exceptiontype == constants.ExceptionDamaged {
		cStatus = constants.ConsignmentDamaged
	}

	if cStatus != "" {
		tx.Model(&models.Consignment{}).Where("consignmentid = ?", req.Consignmentid).Update("status", cStatus)
		
		// Log history
		history := models.ConsignmentHistory{
			Consignmentid: req.Consignmentid,
			Tripsheetid:   req.Tripsheetid,
			Hubid:         req.Hubid,
			Userid:        &adminUserID,
			Eventstatus:   cStatus,
			Remarks:       fmt.Sprintf("Exception reported: %s. Description: %s", req.Exceptiontype, req.Description),
		}
		tx.Create(&history)
	}

	tx.Commit()
	return utils.Created(c, exception)
}

func GetExceptionDetails(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))
	var exception models.ConsignmentException
	if err := db.DB.Where("exceptionid = ? AND deletedat IS NULL", id).First(&exception).Error; err != nil {
		return utils.NotFound(c, "exception not found")
	}
	return utils.OK(c, exception)
}

func ResolveException(c *fiber.Ctx) error {
	id, _ := strconv.Atoi(c.Params("id"))

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

	adminUserID := c.Locals("userid").(int)

	var exception models.ConsignmentException
	if err := db.DB.Where("exceptionid = ? AND deletedat IS NULL", id).First(&exception).Error; err != nil {
		return utils.NotFound(c, "exception not found")
	}

	exception.Resolution = req.Resolution
	exception.Status = req.Status
	exception.Updatedby = adminUserID
	exception.Updatedat = time.Now()

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

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

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

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

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

	jsonData, err := json.Marshal(userData)
	if err != nil {
		return utils.Internal(c, "failed to serialize user data")
	}

	userKey := fmt.Sprintf("user:%d", userData.UserID)
	if err := db.Rdb.Set(ctx, userKey, jsonData, 0).Err(); err != nil {
		return utils.Internal(c, "failed to store user in cache")
	}

	db.Rdb.ZAdd(ctx, "user", redis.Z{
		Score:  float64(time.Now().Unix()),
		Member: fmt.Sprintf("%d", userData.UserID),
	})

	return utils.Created(c, userData)
}

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

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

	userIDStr := c.Query("userid")

	if userIDStr != "" {
		userID, err := strconv.Atoi(userIDStr)
		if err != nil || userID == 0 {
			return utils.BadRequest(c, "invalid userid parameter")
		}

		userKey := fmt.Sprintf("user:%d", userID)
		userData, err := db.Rdb.Get(ctx, userKey).Result()
		if err != nil {
			return utils.NotFound(c, "user not found in cache")
		}

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

		return utils.OK(c, user)
	}

	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
	}

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

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

	var keys []string
	for _, id := range userIDs {
		keys = append(keys, fmt.Sprintf("user:%s", id))
	}

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

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

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

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

	userID, err := strconv.Atoi(c.Params("userid"))
	if err != nil || userID == 0 {
		return utils.BadRequest(c, "invalid userid parameter")
	}

	var userData map[string]interface{}
	if err := c.BodyParser(&userData); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

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

	userKey := fmt.Sprintf("user:%d", userID)
	exists, err := db.Rdb.Exists(ctx, userKey).Result()
	if err != nil || exists == 0 {
		return utils.NotFound(c, "user not found in cache")
	}

	userData["userid"] = userID
	jsonData, _ := json.Marshal(userData)

	if err := db.Rdb.Set(ctx, userKey, jsonData, 0).Err(); err != nil {
		return utils.Internal(c, "failed to update user in cache")
	}

	return utils.OK(c, userData)
}

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

	userID, err := strconv.Atoi(c.Params("userid"))
	if err != nil || userID == 0 {
		return utils.BadRequest(c, "invalid userid parameter")
	}

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

	userKey := fmt.Sprintf("user:%d", userID)
	db.Rdb.Del(ctx, userKey)
	db.Rdb.ZRem(ctx, "user", fmt.Sprintf("%d", userID))

	return utils.Message(c, "user removed from cache successfully")
}

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

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

	userIDs, err := db.Rdb.ZRange(ctx, "user", 0, -1).Result()
	if err != nil {
		return utils.Internal(c, "failed to fetch user index")
	}

	var users []map[string]interface{}
	for _, idStr := range userIDs {
		userKey := fmt.Sprintf("user:%s", idStr)
		userData, err := db.Rdb.Get(ctx, userKey).Result()
		if err != nil {
			continue
		}
		var user map[string]interface{}
		json.Unmarshal([]byte(userData), &user)
		users = append(users, user)
	}

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

func GetAdminProfile(c *fiber.Ctx) error {
	userID, ok := c.Locals("userid").(int)
	if !ok {
		return utils.Unauthorized(c, "authentication required")
	}

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

	return utils.OK(c, user)
}

// InternalNotify sends FCM push notifications on behalf of the Python agent system.
// The caller specifies target = "customer", "miler", or "both".
// Auth: X-Internal-Key header (see InternalKeyAuth middleware).
//
// POST /api/v1/internal/notify
func InternalNotify(c *fiber.Ctx) error {
	type req struct {
		BookingID int               `json:"booking_id"`
		Target    string            `json:"target"`
		Title     string            `json:"title"`
		Message   string            `json:"message"`
		Data      map[string]string `json:"data"`
	}

	body := new(req)
	if err := c.BodyParser(body); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if body.BookingID == 0 {
		return utils.BadRequest(c, "booking_id is required")
	}
	if body.Target != "customer" && body.Target != "miler" && body.Target != "both" {
		return utils.BadRequest(c, "target must be customer, miler, or both")
	}
	if body.Title == "" || body.Message == "" {
		return utils.BadRequest(c, "title and message are required")
	}

	var booking models.PickupBooking
	if err := db.DB.First(&booking, body.BookingID).Error; err != nil {
		return utils.NotFound(c, "booking not found")
	}

	data := body.Data
	if data == nil {
		data = map[string]string{}
	}
	data["booking_id"] = strconv.Itoa(booking.Bookingid)

	sent := make([]string, 0, 2)

	if body.Target == "customer" || body.Target == "both" {
		var customer models.AppCustomer
		if err := db.DB.Where("appcustomerid = ?", booking.Appcustomerid).First(&customer).Error; err == nil && customer.Devicetoken != "" {
			if err := notify.SendToDevice(customer.Devicetoken, body.Title, body.Message, data); err != nil {
				utils.Warn("InternalNotify: failed to notify customer", "booking_id", booking.Bookingid, "error", err)
			} else {
				sent = append(sent, "customer")
			}
		}
	}

	if body.Target == "miler" || body.Target == "both" {
		if booking.Assignedmileruserid != nil {
			var profile models.MilerProfile
			if err := db.DB.Where("userid = ?", *booking.Assignedmileruserid).First(&profile).Error; err == nil && profile.Devicetoken != "" {
				if err := notify.SendToDevice(profile.Devicetoken, body.Title, body.Message, data); err != nil {
					utils.Warn("InternalNotify: failed to notify miler", "booking_id", booking.Bookingid, "error", err)
				} else {
					sent = append(sent, "miler")
				}
			}
		}
	}

	return utils.OK(c, fiber.Map{
		"sent":    true,
		"targets": sent,
	})
}

// InternalReassign releases the current miler assignment and re-triggers the
// auto-assignment engine for a stalled booking. Only valid when the booking is
// in Miler_Assigned or Pickup_Scheduled state.
// Auth: X-Internal-Key header (see InternalKeyAuth middleware).
//
// POST /api/v1/internal/bookings/:id/reassign
func InternalReassign(c *fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return utils.BadRequest(c, "invalid booking ID")
	}

	tx := db.DB.Begin()

	var booking models.PickupBooking
	if err := tx.First(&booking, id).Error; err != nil {
		tx.Rollback()
		return utils.NotFound(c, "booking not found")
	}

	if booking.Status != constants.BookingMilerAssigned && booking.Status != constants.BookingPickupScheduled {
		tx.Rollback()
		return utils.BadRequest(c, "booking must be in Miler_Assigned or Pickup_Scheduled state to reassign")
	}

	now := time.Now()

	if booking.Assignedmileruserid != nil {
		tx.Model(&models.MilerProfile{}).
			Where("userid = ?", *booking.Assignedmileruserid).
			Updates(map[string]interface{}{
				"availabilitystatus": constants.MilerAvailable,
				"updatedat":          now,
			})

		tx.Delete(&models.BookingAssignment{},
			"bookingid = ? AND assignmentstatus IN ?",
			booking.Bookingid,
			[]string{constants.AssignmentAssigned, constants.AssignmentAccepted},
		)
	}

	booking.Status = constants.BookingCreated
	booking.Assignedmileruserid = nil
	booking.Updatedat = now
	if err := tx.Save(&booking).Error; err != nil {
		tx.Rollback()
		return utils.Internal(c, "failed to reset booking")
	}

	tx.Commit()

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

	utils.Info("InternalReassign: reassignment triggered",
		"booking_id", booking.Bookingid,
		"booking_source", booking.Bookingsource,
	)

	return utils.OK(c, fiber.Map{
		"reassignment": "triggered",
		"booking_id":   booking.Bookingid,
	})
}
