package domain

import (
	"context"
	"encoding/json"
	"errors"
	"nearle/db"
	"nearle/models"
	"nearle/utils"
	"strconv"
	"strings"
	"time"

	"github.com/jinzhu/copier"
	"github.com/redis/go-redis/v9"
)

const (
	deliveries = `SELECT distinct a.deliveryid,a.applocationid,f.locationname AS applocation,a.orderheaderid,a.configid,a.tenantid,a.partnerid,a.locationid,a.userid,a.categoryid,a.subcategoryid,a.moduleid,
	a.orderid,a.deliverydate,a.orderstatus,a.assigntime,a.starttime,a.arrivaltime,a.pickuptime,a.deliverytime,a.canceltime,a.acceptedtime,
	a.customerid,a.pickupcustomer,a.pickupcontactno,a.pickuplocationid,a.pickupaddress,a.pickuplocation,a.pickuplat,a.pickuplon,
	a.deliverycustomerid,a.deliverycustomer,a.deliverycontactno,a.deliverylocationid,a.deliveryaddress,a.deliverylocation,
	a.droplat,a.droplon,a.deliverylat,a.deliverylong,
	a.riderslat,a.riderslon,a.deliveryamt,a.kms,a.actualkms,a.riderkms,a.deliverycharges,a.deliverytype,a.paymenttype,a.smsdelivery,a.quantity,a.collectionamt,a.collectedamt,a.collectionstatus,
	a.notes,a.ordernotes,a.step,a.eta,a.previouskms,a.cumulativekms,a.ridertime,b.tenantname,b.primarycontact as tenantcontactno,b.tenanttoken,b.suburb as tenantsuburb,b.city as tenantcity,
	b.address AS tenantaddress, CONCAT(c.firstname, ' ', c.lastname) AS ridername,c.userfcmtoken,c.contactno as ridercontact,e.locationname,e.suburb AS locationsuburb,e.contactno AS locationcontactno,e.address AS locationaddress,
	h.slab, h.pricingdate, h.baseprice, h.minkm, h.priceperkm, h.maxkm, h.orders, h.othercharges, h.surgecharges,
	a.expecteddeliverytime, a.profit, a.transitminutes, a.calculationdistancekm
	FROM deliveries a
	INNER JOIN tenants b ON a.tenantid=b.tenantid
	INNER JOIN app_users c ON a.userid=c.userid
	INNER JOIN tenantlocations e ON a.locationid=e.locationid
	INNER JOIN app_location f ON a.applocationid = f.applocationid
	INNER JOIN app_locationconfig g ON f.applocationid = g.applocationid
	LEFT JOIN tenantpricing h ON a.tenantid = h.tenantid`
)

func PublishLog(input []models.Deliverylogs) error {

	logInfo("PublishLog", "records", len(input))

	tx := db.DB.Begin()

	for i := range input {

		t1 := tx.Create(&input[i])
		if t1.Error != nil {
			logError("PublishLog failed", "error", tx.Error)
			tx.Rollback()
			return t1.Error
		}

	}

	err := tx.Commit().Error
	if err != nil {

		return err
	}

	return nil

}

func CreateDeliveries(data []models.Deliveries) error {
	logInfo("CreateDeliveries", "records", len(data))

	var que []models.Deliveryqueues

	var ord models.Updateorderstatus

	tx := db.DB.Begin()

	for i := range data {

		t1 := tx.Create(&data[i])
		if t1.Error != nil {
			logError("CreateDeliveries failed", "error", tx.Error)
			tx.Rollback()
			return t1.Error
		}

		if err := copier.Copy(&que, &data[i]); err != nil {
			return err
		}

		t2 := tx.Create(&que)
		if t2.Error != nil {
			logError("CreateDeliveries queue failed", "error", tx.Error)
			tx.Rollback()
			return t2.Error
		}
		ord.Orderstatus = data[i].Orderstatus
		ord.Pending = data[i].Deliverydate

		t3 := tx.Table("orders").Where("orderheaderid=?", data[i].Orderheaderid).Updates(&ord)
		if t3.Error != nil {

			logError("CreateDeliveries order update failed", "error", tx.Error)
			tx.Rollback()
			return t3.Error

		}

	}

	err := tx.Commit().Error
	if err != nil {

		return err
	}

	return nil

}

func CreateDeliveriesV2(data []models.Deliveries) ([]int64, error) {

	ctx := db.Ctx
	var deliveryIDs []int64

	logInfo("🔥 CreateDeliveriesV2 DOMAIN CALLED")

	for _, d := range data {

		// 🔒 Rider guard key
		riderKey := "rider:" + strconv.Itoa(d.Userid) + ":active_delivery"

		// ⏱ delivery TTL (example: 2 hours)
		ttl := 2 * time.Hour

		// 🔒 PREVENT DOUBLE ASSIGN (CREATE ONLY IF NOT EXISTS)
		ok, err := db.Rdb.SetNX(ctx, riderKey, "LOCK", ttl).Result()
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, errors.New("rider " + strconv.Itoa(d.Userid) + " already has active delivery")
		}

		// 🔥 Generate delivery ID
		deliveryID, err := db.Rdb.Incr(ctx, "counter:deliveryid").Result()
		if err != nil {
			db.Rdb.Del(ctx, riderKey)
			return nil, err
		}

		deliveryIDs = append(deliveryIDs, deliveryID)

		// 🔑 Redis keys
		deliveryKey := "delivery:active:" + strconv.FormatInt(deliveryID, 10)
		cityKey := "city:" + strconv.Itoa(d.Tenantid) + ":active_deliveries"

		pipe := db.Rdb.TxPipeline()

		// 🔥 ACTIVE DELIVERY HASH (AUTO-CREATES)
		pipe.HSet(ctx, deliveryKey, map[string]interface{}{
			"delivery_id":           deliveryID,
			"orderheaderid":         d.Orderheaderid,
			"orderid":               d.Orderid,
			"tenantid":              d.Tenantid, // city
			"partnerid":             d.Partnerid,
			"rider_id":              d.Userid,
			"status":                "ASSIGNED",
			"pickupaddress":         d.Pickupaddress,
			"deliveryaddress":       d.Deliveryaddress,
			"pickuplat":             d.Pickuplat,
			"pickuplon":             d.Pickuplon,
			"deliverylat":           d.Deliverylat,
			"deliverylong":          d.Deliverylong,
			"eta":                   d.Eta,
			"expecteddeliverytime":  d.Expecteddeliverytime,
			"profit":                d.Profit,
			"transitminutes":        d.Transitminutes,
			"calculationdistancekm": d.Calculationdistancekm,
			"created_at":            time.Now().Unix(),
			"last_updated":          time.Now().Unix(),
		})

		// 🔥 CITY → ACTIVE DELIVERIES INDEX (AUTO-CREATES)
		pipe.SAdd(ctx, cityKey, deliveryID)

		// 🔥 SET RIDER VALUE (REPLACE LOCK)
		pipe.Set(ctx, riderKey, deliveryID, ttl)

		// 🔥 TTL ONLY ON DELIVERY & RIDER (NEVER CITY)
		pipe.Expire(ctx, deliveryKey, ttl)

		cmds, err := pipe.Exec(ctx)
		if err != nil {
			db.Rdb.Del(ctx, riderKey)
			return nil, err
		}

		// 🔍 Per-command safety check
		for _, cmd := range cmds {
			if cmd.Err() != nil {
				db.Rdb.Del(ctx, riderKey)
				return nil, cmd.Err()
			}
		}

		logInfo("✅ Delivery created", "deliveryID", deliveryID)
	}

	logInfo("✅ CreateDeliveriesV2 COMPLETED")
	return deliveryIDs, nil
}

func CreateDelivery(data models.Deliveries) error {
	logInfo("CreateDelivery", "orderheaderid", data.Orderheaderid)

	var que models.Deliveryqueues
	var ord models.Updateorderstatus

	tx := db.DB.Begin()

	t1 := tx.Table("deliveries").Create(&data)
	if t1.Error != nil {

		tx.Rollback()
		return t1.Error
	}

	if err := copier.Copy(&que, &data); err != nil {

		return err
	}

	t2 := tx.Table("deliveryqueues").Create(&que)
	if t2.Error != nil {

		logError("CreateDelivery queue failed", "error", tx.Error)
		tx.Rollback()
		return t2.Error
	}

	ord.Orderstatus = data.Orderstatus
	ord.Pending = data.Assigntime

	t3 := tx.Table("orders").Where("orderheaderid=?", data.Orderheaderid).Updates(&ord)
	if t3.Error != nil {

		logError("CreateDelivery order update failed", "error", tx.Error)
		tx.Rollback()
		return t3.Error
	}

	err := tx.Commit().Error
	if err != nil {

		return err

	}
	return nil

}

func UpdateDelivery(data models.UpdateDeliveryStatus) error {
	logInfo("UpdateDelivery", "deliveryid", data.Deliveryid, "status", data.Orderstatus)

	var ord models.Updateorderstatus
	var cloc models.Customerlocations

	tx := db.DB.Begin()

	t1 := tx.Table("deliveries").Where("deliveryid=?", data.Deliveryid).Updates(&data)
	if t1.Error != nil {

		logError("UpdateDelivery failed", "error", tx.Error)
		tx.Rollback()
	}

	if data.Orderstatus == "pending" {

		ord.Orderstatus = data.Orderstatus
		ord.Pending = data.Assigntime

		t2 := tx.Table("orders").Where("orderheaderid=?", data.Orderheaderid).Updates(&ord)
		if t2.Error != nil {

			logError("UpdateDelivery pending update failed", "error", tx.Error)
			tx.Rollback()
		}

	}

	if data.Orderstatus == "picked" {

		if data.Pickuplocationid != 0 && data.Deliverytype != "B" {

			cloc.Latitude = data.Riderslat
			cloc.Longitude = data.Riderslon
			cloc.Address = data.Address
			cloc.Suburb = data.Suburb
			cloc.City = data.City
			cloc.State = data.State
			cloc.Postcode = data.Postcode

			t5 := tx.Table("customerlocations").Where("locationid=?", data.Pickuplocationid).Updates(&cloc)
			if t5.Error != nil {

				logError("UpdateDelivery customer location update failed", "error", tx.Error)
				tx.Rollback()
			}

		}

	}

	if data.Orderstatus == "delivered" {

		ord.Orderstatus = data.Orderstatus
		ord.Delivered = data.Deliverytime

		t2 := tx.Table("orders").Where("orderheaderid=?", data.Orderheaderid).Updates(&ord)
		if t2.Error != nil {

			logError("UpdateDelivery delivered update failed", "error", tx.Error)
			tx.Rollback()
		}

		if data.Deliverylocationid != 0 && data.Deliverytype != "B" {

			cloc.Latitude = data.Deliverylat
			cloc.Longitude = data.Deliverylong
			cloc.Address = data.Address
			cloc.Suburb = data.Suburb
			cloc.City = data.City
			cloc.State = data.State
			cloc.Postcode = data.Postcode

			t5 := tx.Table("customerlocations").Where("locationid=?", data.Deliverylocationid).Updates(&cloc)
			if t5.Error != nil {

				logError("UpdateDelivery delivery location update failed", "error", tx.Error)
				tx.Rollback()
			}

		}

	}

	if data.Orderstatus == "cancelled" {

		ord.Orderstatus = data.Orderstatus
		ord.Cancelled = data.Canceltime

		t2 := tx.Table("orders").Where("orderheaderid=?", data.Orderheaderid).Updates(&ord)
		if t2.Error != nil {

			logError("UpdateDelivery cancelled update failed", "error", tx.Error)
			tx.Rollback()
		}

	}

	err := tx.Commit().Error

	if err != nil {

		return err

	}

	return err

}

func GetDeliveryQueues(uid int, fdate, tdate string) []models.Deliveryinfo {
	logInfo("GetDeliveryQueues", "userid", uid)

	var data []models.Deliveryinfo

	var q1 string

	if fdate != "" && tdate != "" {

		q1 = deliveries + " where a.orderstatus='pending' and a.userid= ? and a.deliverydate::date between ? and ?  order by a.deliveryid asc"
		db.DB.Raw(q1, uid, fdate, tdate).Find(&data)

	} else {

		q1 = deliveries + " where a.orderstatus='pending' and a.userid= ? and a.deliverydate::date = CURRENT_DATE order by a.deliveryid asc"
		db.DB.Raw(q1, uid).Find(&data)
	}

	logInfo("GetDeliveryQueues Query executed", "userid", uid)

	return data

}

func GetDeliveryQueuesV1(uid int, fdate, tdate string) []models.Deliveryinfo {
	logInfo("GetDeliveryQueuesV1", "userid", uid)
	var data []models.Deliveryinfo
	var q1 string

	statusFilter := `a.orderstatus IN ('pending', 'accepted', 'arrived')`

	if fdate != "" && tdate != "" {
		q1 = deliveries + " WHERE " + statusFilter + " AND a.userid = ? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid ASC"
		db.DB.Raw(q1, uid, fdate, tdate).Find(&data)
	} else {
		q1 = deliveries + " WHERE " + statusFilter + " AND a.userid = ? AND a.deliverydate::date = CURRENT_DATE ORDER BY a.deliveryid ASC"
		db.DB.Raw(q1, uid).Find(&data)
	}

	return data
}

func GetDeliveryQueuesPicked(uid int, fdate, tdate string) []models.Deliveryinfo {
	logInfo("GetDeliveryQueuesPicked", "userid", uid)

	var data []models.Deliveryinfo

	var q1 string

	if fdate != "" && tdate != "" {

		q1 = deliveries + " where a.orderstatus='picked' and a.userid=? and a.deliverydate::date between ? and ?  order by a.deliveryid asc"
		db.DB.Raw(q1, uid, fdate, tdate).Find(&data)

	} else {

		q1 = deliveries + " where a.orderstatus='picked' and a.userid=? and a.deliverydate::date = CURRENT_DATE order by a.deliveryid asc"
		db.DB.Raw(q1, uid).Find(&data)
	}

	return data

}

func GetDeliverylogs(did int) []models.Deliverylogs {
	logInfo("GetDeliverylogs", "deliveryid", did)

	var data []models.Deliverylogs

	q1 := "select * from deliverylogs where deliveryid=? order by logid asc"
	db.DB.Raw(q1, did).Find(&data)

	return data

}

func GetTenantDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {
	logInfo("🔍 Fetching Tenant Deliveries", "tenantid", input.Tenantid)

	var data []models.Deliveryinfo
	var queryBuilder strings.Builder
	var params []interface{}

	offset := (input.Pageno - 1) * input.Pagesize

	baseQuery := `
	SELECT a.*, b.tenantname, 
	       CONCAT(c.firstname, ' ', c.lastname) AS ridername, 
	       c.contactno AS ridercontact, c.userfcmtoken,
	       d.locationname, d.contactno AS locationcontactno, 
	       d.suburb AS locationsuburb, d.address AS locationaddress,
	       e.slab, e.pricingdate, e.baseprice, e.minkm, 
	       e.priceperkm, e.maxkm, e.orders, 
	       e.othercharges, e.surgecharges
	FROM deliveries a
	JOIN tenants b ON a.tenantid = b.tenantid
	JOIN app_users c ON a.userid = c.userid
	JOIN tenantlocations d ON a.locationid = d.locationid
	JOIN tenantpricing e ON a.tenantid = e.tenantid
	WHERE b.moduleid = 6
	  AND a.tenantid = ?
	`
	queryBuilder.WriteString(baseQuery)
	params = append(params, input.Tenantid)

	// ✅ USERID FILTER (FIX)
	if input.UserID > 0 {
		queryBuilder.WriteString(" AND a.userid = ?")
		params = append(params, input.UserID)
	}

	// ✅ STATUS FILTER (ignore "all")
	if input.Status != "" && strings.ToLower(input.Status) != "all" {
		queryBuilder.WriteString(" AND LOWER(a.orderstatus) = LOWER(?)")
		params = append(params, input.Status)
	}

	// ✅ DATE FILTER
	if input.Fromdate != "" && input.ToDate != "" {
		queryBuilder.WriteString(" AND a.deliverydate::date BETWEEN ? AND ?")
		params = append(params, input.Fromdate, input.ToDate)
	}

	// ✅ KEYWORD FILTER
	if input.Keyword != "" {
		k := "%" + strings.ToLower(input.Keyword) + "%"

		queryBuilder.WriteString(`
			AND (
				LOWER(a.pickupcustomer) LIKE ? OR
				LOWER(a.deliverycustomer) LIKE ? OR
				LOWER(a.pickupcontactno) LIKE ? OR
				LOWER(a.deliverycontactno) LIKE ? OR
				LOWER(CAST(a.orderid AS CHAR)) LIKE ? OR
				LOWER(c.firstname) LIKE ? OR
				LOWER(c.lastname) LIKE ? OR
				LOWER(c.contactno) LIKE ?
			)
		`)

		for i := 0; i < 8; i++ {
			params = append(params, k)
		}
	}

	queryBuilder.WriteString(" ORDER BY a.deliveryid DESC")

	if input.Pagesize > 0 {
		queryBuilder.WriteString(" LIMIT ? OFFSET ?")
		params = append(params, input.Pagesize, offset)
	}

	query := queryBuilder.String()
	// print("Final Query:", query)
	db.DB.Raw(query, params...).Scan(&data)
	return data
}

func GetPartnerDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {

	logInfo("Partner Deliveries", "partnerid", input.Partnerid)

	var data []models.Deliveryinfo
	var qb strings.Builder
	var params []interface{}

	offset := (input.Pageno - 1) * input.Pagesize

	// 🔹 Base query
	qb.WriteString(deliveries)
	qb.WriteString(` WHERE b.moduleid = 6 AND a.partnerid = ?`)
	params = append(params, input.Partnerid)

	// 🔹 STATUS FILTER (ignore "all")
	if input.Status != "" && strings.ToLower(input.Status) != "all" {
		qb.WriteString(` AND LOWER(a.orderstatus) = LOWER(?)`)
		params = append(params, input.Status)
	}

	// 🔹 DATE FILTER
	if input.Fromdate != "" && input.ToDate != "" {
		qb.WriteString(` AND a.deliverydate::date BETWEEN ? AND ?`)
		params = append(params, input.Fromdate, input.ToDate)
	}

	// 🔹 ORDER
	qb.WriteString(` ORDER BY a.deliveryid DESC`)

	// 🔹 PAGINATION
	if input.Pagesize > 0 {
		qb.WriteString(` LIMIT ? OFFSET ?`)
		params = append(params, input.Pagesize, offset)
	}

	finalQuery := qb.String()
	logInfo("Final Query", "query", finalQuery)
	logInfo("Params", "params", params)

	db.DB.Raw(finalQuery, params...).Scan(&data)
	return data
}

func GetCustomerDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {

	logInfo("Customer Deliveries", "partnerid", input.Partnerid)

	var q1 string
	offset := (input.Pageno - 1) * input.Pagesize
	var data []models.Deliveryinfo

	// ✅ Apply status filter ONLY if status != "" AND status != "all"
	if input.Status != "" && strings.ToLower(input.Status) != "all" {

		if input.Fromdate != "" && input.ToDate != "" {

			if input.Pagesize != 0 {

				q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid desc LIMIT ? OFFSET ?"
				db.DB.Raw(q1, input.Partnerid, input.Status, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data)

			} else {

				q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid desc"
				db.DB.Raw(q1, input.Partnerid, input.Status, input.Fromdate, input.ToDate).Find(&data)
			}

		} else {

			if input.Pagesize != 0 {

				q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.orderstatus=? order by deliveryid desc LIMIT ? OFFSET ?"
				db.DB.Raw(q1, input.Partnerid, input.Status, input.Pagesize, offset).Find(&data)

			} else {

				q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.orderstatus=? order by deliveryid desc"
				db.DB.Raw(q1, input.Partnerid, input.Status).Find(&data)
			}
		}

	} else {
		// ✅ status = "" OR status = "all" → NO STATUS FILTER

		if input.Pagesize != 0 {

			q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.deliverydate::date between ? and ? order by deliveryid desc LIMIT ? OFFSET ?"
			db.DB.Raw(q1, input.Partnerid, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data)

		} else {

			q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.deliverydate::date between ? and ? order by deliveryid desc"
			db.DB.Raw(q1, input.Partnerid, input.Fromdate, input.ToDate).Find(&data)
		}
	}
	return data
}

func GetAdminDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {
	logInfo("Admin Deliveries", "applocationid", input.Applocationid)

	var data []models.Deliveryinfo
	var qb strings.Builder
	var params []interface{}

	offset := (input.Pageno - 1) * input.Pagesize

	qb.WriteString(`
		SELECT a.*, 
		       b.tenantname,b.primarycontact as tenantcontactno,b.tenanttoken,b.suburb as tenantsuburb,b.city as tenantcity, b.address AS tenantaddress,
		       CONCAT(c.firstname, ' ', c.lastname) AS ridername,c.userfcmtoken,c.contactno as ridercontact,
		       d.locationname,d.suburb AS locationsuburb,d.contactno AS locationcontactno,d.address AS locationaddress,
		       e.locationname AS applocation, f.slab, f.pricingdate, f.baseprice, f.minkm, f.priceperkm, f.maxkm, f.orders, f.othercharges, f.surgecharges
		FROM deliveries a
		JOIN tenants b ON a.tenantid = b.tenantid
		JOIN app_users c ON a.userid = c.userid
		JOIN tenantlocations d ON a.locationid = d.locationid
		JOIN app_location e ON a.applocationid = e.applocationid
		JOIN tenantpricing f ON a.tenantid = f.tenantid
		WHERE b.moduleid = 6
		  AND a.applocationid = ?
	`)
	params = append(params, input.Applocationid)

	// ✅ TENANT FILTER
	if input.Tenantid != 0 {
		qb.WriteString(" AND a.tenantid = ?")
		params = append(params, input.Tenantid)
	}

	// ✅ LOCATION FILTER
	if input.Locationid != 0 {
		qb.WriteString(" AND a.locationid = ?")
		params = append(params, input.Locationid)
	}

	// ✅ USER / RIDER FILTER
	if input.UserID != 0 {
		qb.WriteString(" AND a.userid = ?")
		params = append(params, input.UserID)
	}

	// ✅ STATUS FILTER
	if input.Status != "" && strings.ToLower(input.Status) != "all" {
		qb.WriteString(" AND LOWER(a.orderstatus) = LOWER(?)")
		params = append(params, input.Status)
	}

	// ✅ DATE RANGE FILTER
	if input.Fromdate != "" && input.ToDate != "" {
		qb.WriteString(`
			AND a.deliverydate >= (CONCAT(?::text, ' 00:00:00'))::timestamp
			AND a.deliverydate <= (CONCAT(?::text, ' 23:59:59'))::timestamp
		`)
		params = append(params, input.Fromdate, input.ToDate)
	}

	qb.WriteString(" ORDER BY a.deliveryid DESC")

	// ✅ PAGINATION
	if input.Pagesize > 0 {
		qb.WriteString(" LIMIT ? OFFSET ?")
		params = append(params, input.Pagesize, offset)
	}

	finalQuery := qb.String()

	db.DB.Raw(finalQuery, params...).Scan(&data)
	return data
}

func GetUserDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {

	var q1 string
	logInfo("User Deliveries", "userid", input.UserID)

	offset := (input.Pageno - 1) * input.Pagesize
	var data []models.Deliveryinfo
	var params []interface{}

	q1 = deliveries + " where b.moduleid=6 and a.userid=?"
	params = append(params, input.UserID)

	// CASE 1: status = all → show all statuses
	if input.Status == "all" {

		q1 += " and a.deliverydate::date between ? and ? order by deliveryid desc"
		params = append(params, input.Fromdate, input.ToDate)

		// CASE 2: Specific status (picked, active, delivered…)
	} else if input.Status != "" {

		q1 += " and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid desc"
		params = append(params, input.Status, input.Fromdate, input.ToDate)

		// CASE 3: No status passed → default statuses
	} else {

		q1 += " and a.orderstatus in ('picked','active','skipped') and a.deliverydate::date between ? and ? order by deliveryid desc"
		params = append(params, input.Fromdate, input.ToDate)
	}

	if input.Pagesize != 0 {
		q1 += " LIMIT ? OFFSET ?"
		params = append(params, input.Pagesize, offset)
	}

	db.DB.Raw(q1, params...).Find(&data)

	return data
}

func GetUserDeliveriesv1(input models.DeliveryQuery) []models.Deliveryinfo {

	var q1 string

	logInfo("User Deliveriesv1", "userid", input.UserID)

	offset := (input.Pageno - 1) * input.Pagesize

	var data []models.Deliveryinfo

	if input.Status != "" {

		if input.Pagesize != 0 {

			logInfo("Rider Deliveriesv2")

			q1 = deliveries + " where a.userid=? and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid desc LIMIT ? OFFSET ?"
			db.DB.Raw(q1, input.UserID, input.Status, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data)

		} else {

			logInfo("Rider Deliveriesv1")

			q1 = deliveries + " where a.userid=? and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid asc"
			db.DB.Raw(q1, input.UserID, input.Status, input.Fromdate, input.ToDate).Find(&data)

		}

	} else {

		if input.Pagesize != 0 {
			logInfo("Rider Deliveriesv2")

			q1 = deliveries + " where a.orderstatus in ('picked') and a.userid=? and a.deliverydate::date between ? and ? order by deliveryid desc LIMIT ? OFFSET ?"
			db.DB.Raw(q1, input.UserID, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data)

		} else {

			logInfo("Rider Deliveriesv1")

			q1 = deliveries + " where a.orderstatus in ('picked') and a.userid=? and a.deliverydate::date between ? and ? order by deliveryid desc"
			db.DB.Raw(q1, input.UserID, input.Fromdate, input.ToDate).Find(&data)

		}

	}

	return data

}

func GetAppUserDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {
	var data []models.Deliveryinfo

	logInfo("App User Deliveries", "appuserid", input.UserID)

	var (
		q1     strings.Builder
		params []interface{}
	)

	offset := (input.Pageno - 1) * input.Pagesize

	// 🔹 Base query
	q1.WriteString(deliveries)
	q1.WriteString(" WHERE b.moduleid = 6 AND g.status = 'Active' ")

	// 🔹 Apply userid filter ONLY if userid > 0
	if input.UserID > 0 {
		q1.WriteString(" AND g.userid = ? ")
		params = append(params, input.UserID)
	}

	// 🔹 Status filter (IGNORE if status = all)
	if input.Status != "" && strings.ToLower(input.Status) != "all" {
		q1.WriteString(" AND a.orderstatus = ? ")
		params = append(params, input.Status)
	}

	// 🔹 Date filter
	if input.Fromdate != "" && input.ToDate != "" {
		q1.WriteString(" AND a.deliverydate::date BETWEEN ? AND ? ")
		params = append(params, input.Fromdate, input.ToDate)
	}

	// 🔹 Keyword filter (case-insensitive)
	if input.Keyword != "" {
		k := "%" + strings.ToLower(input.Keyword) + "%"

		q1.WriteString(`
			AND (
				LOWER(a.pickupcustomer) LIKE ? OR
				LOWER(b.tenantname) LIKE ? OR
				LOWER(a.deliverycustomer) LIKE ? OR
				LOWER(a.pickupcontactno) LIKE ? OR
				LOWER(a.deliverycontactno) LIKE ? OR
				LOWER(CAST(a.orderid AS CHAR)) LIKE ? OR
				LOWER(CAST(a.deliveryid AS CHAR)) LIKE ? OR
				LOWER(c.firstname) LIKE ? OR
				LOWER(c.contactno) LIKE ?
			)
		`)

		for i := 0; i < 9; i++ {
			params = append(params, k)
		}
	}

	q1.WriteString(" ORDER BY a.deliveryid DESC ")

	if input.Pagesize > 0 {
		q1.WriteString(" LIMIT ? OFFSET ? ")
		params = append(params, input.Pagesize, offset)
	}

	db.DB.Raw(q1.String(), params...).Scan(&data)

	return data
}

func GetDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {
	var data []models.Deliveryinfo
	var queryBuilder strings.Builder
	var params []interface{}

	logInfo("Get All Deliveries -new")

	offset := (input.Pageno - 1) * input.Pagesize

	baseQuery := `
	SELECT a.*, b.tenantname, c.userid,
	       CONCAT(c.firstname, ' ', c.lastname) AS ridername,
	       c.contactno AS ridercontact,
	       d.slab, d.pricingdate, d.baseprice, d.minkm,
	       d.priceperkm, d.maxkm, d.orders,
	       d.othercharges, d.surgecharges
	FROM deliveries a
	JOIN tenants b ON a.tenantid = b.tenantid
	JOIN app_users c ON a.userid = c.userid
	JOIN tenantpricing d ON a.tenantid = d.tenantid
	WHERE 1=1
	`

	queryBuilder.WriteString(baseQuery)

	// ✅ DATE FILTER
	if input.Fromdate != "" && input.ToDate != "" {
		queryBuilder.WriteString(" AND b.moduleid=6 AND a.deliverydate::date BETWEEN ? AND ?")
		params = append(params, input.Fromdate, input.ToDate)
	}

	// ✅ STATUS FILTER (ignore "all")
	if input.Status != "" && strings.ToLower(input.Status) != "all" {
		queryBuilder.WriteString(" AND b.moduleid=6 AND LOWER(a.orderstatus) = LOWER(?)")
		params = append(params, input.Status)
	}

	// ✅ KEYWORD FILTER
	if input.Keyword != "" {
		k := "%" + strings.ToLower(input.Keyword) + "%"

		queryBuilder.WriteString(`
			AND (
				LOWER(a.pickupcustomer) LIKE ? OR
				LOWER(b.tenantname) LIKE ? OR
				LOWER(a.deliverycustomer) LIKE ? OR
				LOWER(a.pickupcontactno) LIKE ? OR
				LOWER(a.deliverycontactno) LIKE ? OR
				LOWER(CAST(a.orderid AS CHAR)) LIKE ?
			)
		`)

		for i := 0; i < 6; i++ {
			params = append(params, k)
		}
	}

	queryBuilder.WriteString(" ORDER BY a.deliveryid DESC")

	if input.Pagesize > 0 {
		queryBuilder.WriteString(" LIMIT ? OFFSET ?")
		params = append(params, input.Pagesize, offset)
	}

	query := queryBuilder.String()

	db.DB.Raw(query, params...).Scan(&data)
	return data
}

func GetTenantLocationDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {

	logInfo("Tenant Location Deliveries", "tenantid", input.Tenantid, "locationid", input.Locationid)

	var data []models.Deliveryinfo
	var queryBuilder strings.Builder
	var params []interface{}

	offset := (input.Pageno - 1) * input.Pagesize

	// 🔹 Base query
	queryBuilder.WriteString(deliveries)
	queryBuilder.WriteString(` WHERE b.moduleid = 6 AND a.tenantid = ? AND a.locationid = ?`)
	params = append(params, input.Tenantid, input.Locationid)

	// 🔹 USER FILTER (apply only if userid > 0)
	if input.UserID > 0 {
		queryBuilder.WriteString(` AND a.userid = ?`)
		params = append(params, input.UserID)
	}

	// 🔹 STATUS FILTER (ignore "all", case-insensitive)
	if input.Status != "" && strings.ToLower(input.Status) != "all" {
		queryBuilder.WriteString(` AND LOWER(a.orderstatus) = LOWER(?)`)
		params = append(params, input.Status)
	}

	// 🔹 DATE FILTER
	if input.Fromdate != "" && input.ToDate != "" {
		queryBuilder.WriteString(` AND a.deliverydate::date BETWEEN ? AND ?`)
		params = append(params, input.Fromdate, input.ToDate)
	}

	// 🔹 KEYWORD FILTER (case-insensitive)
	if input.Keyword != "" {
		k := "%" + strings.ToLower(input.Keyword) + "%"

		queryBuilder.WriteString(`
			AND (
				LOWER(a.pickupcustomer) LIKE ? OR
				LOWER(b.tenantname) LIKE ? OR
				LOWER(a.deliverycustomer) LIKE ? OR
				LOWER(a.pickupcontactno) LIKE ? OR
				LOWER(a.deliverycontactno) LIKE ? OR
				LOWER(CAST(a.orderid AS CHAR)) LIKE ?
			)
		`)

		// ✔ exactly 6 placeholders
		for i := 0; i < 6; i++ {
			params = append(params, k)
		}
	}

	// 🔹 ORDER + PAGINATION
	queryBuilder.WriteString(` ORDER BY a.deliveryid DESC`)

	if input.Pagesize > 0 {
		queryBuilder.WriteString(` LIMIT ? OFFSET ?`)
		params = append(params, input.Pagesize, offset)
	}

	finalQuery := queryBuilder.String()
	logInfo("Final Query", "query", finalQuery)
	logInfo("Params", "params", params)

	db.DB.Raw(finalQuery, params...).Scan(&data)
	return data
}

func GetLocationDeliveries(input models.DeliveryQuery) []models.Deliveryinfo {

	logInfo("Location Deliveries", "locationid", input.Locationid)

	var q1 string
	var data []models.Deliveryinfo
	offset := (input.Pageno - 1) * input.Pagesize

	if input.Status != "" {

		if input.Fromdate != "" && input.ToDate != "" {

			if input.Pagesize != 0 {

				q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.orderstatus=? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid DESC LIMIT ? OFFSET ?"
				db.DB.Raw(q1, input.Locationid, input.Status, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data)

			} else {

				q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.orderstatus=? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid DESC"
				db.DB.Raw(q1, input.Locationid, input.Status, input.Fromdate, input.ToDate).Find(&data)
			}

		} else {

			if input.Pagesize != 0 {

				q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.orderstatus=? ORDER BY a.deliveryid DESC LIMIT ? OFFSET ?"
				db.DB.Raw(q1, input.Locationid, input.Status, input.Pagesize, offset).Find(&data)

			} else {

				q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.orderstatus=? ORDER BY a.deliveryid DESC"
				db.DB.Raw(q1, input.Locationid, input.Status).Find(&data)
			}
		}

	} else {

		if input.Pagesize != 0 {

			q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid DESC LIMIT ? OFFSET ?"
			db.DB.Raw(q1, input.Locationid, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data)

		} else {

			q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid DESC"
			db.DB.Raw(q1, input.Locationid, input.Fromdate, input.ToDate).Find(&data)
		}
	}
	return data
}

// func PublishLogv1(input []models.Deliverylogs) error {
// 	key := "Deliverylogs"

// 	for _, item := range input {
// 		// Convert each item to JSON
// 		jsonData, err := json.Marshal(item)
// 		if err != nil {
// 			return fmt.Errorf("failed to marshal delivery log: %v", err)
// 		}

// 		// Push each record into Redis list
// 		if err := db.Rdb.RPush(db.Ctx, key, jsonData).Err(); err != nil {
// 			return fmt.Errorf("failed to push to Redis: %v", err)
// 		}
// 	}

// 	return nil
// }

// func PublishLogv1(input []models.Deliverylogs) error {

// 	for _, item := range input {

// 		// key per delivery
// 		key := fmt.Sprintf("Deliverylogs:%d", item.Deliveryid)

// 		// Convert to JSON
// 		jsonData, err := json.Marshal(item)
// 		if err != nil {
// 			return fmt.Errorf("marshal error: %v", err)
// 		}

// 		// Write to delivery-specific list
// 		if err := db.Rdb.RPush(db.Ctx, key, jsonData).Err(); err != nil {
// 			return fmt.Errorf("redis push error: %v", err)
// 		}
// 	}

// 	return nil
// }

func PublishLogv1(input []models.Deliverylogs) error {
	logInfo("PublishLogv1", "records", len(input))

	pipe := db.Rdb.TxPipeline()

	for _, item := range input {

		logInfo("DELIVERY LOG RECEIVED",
			"deliveryid", item.Deliveryid,
			"userid", item.Userid,
			"logdate", item.Logdate,
		)

		logTime, err := time.Parse("2006-01-02 15:04:05", item.Logdate)
		if err != nil {
			logError("❌ TIME PARSE FAILED", "error", err)
			continue
		}

		ts := logTime.Unix()

		logKey := "Deliverylogs:" + strconv.Itoa(item.Deliveryid)
		userIndexKey := "user:deliverylogs:" + 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.Deliveryid,
		})

		logInfo("✅ ZADD PREPARED", "key", userIndexKey, "deliveryid", item.Deliveryid)
	}

	_, err := pipe.Exec(db.Ctx)
	if err != nil {
		logError("❌ PIPELINE EXEC FAILED", "error", err)
	}

	return err
}

func GetDeliverylogsv1(did int) ([]models.Deliverylogs, error) {
	logInfo("GetDeliverylogsv1", "deliveryid", did)
	var result []models.Deliverylogs

	key := "Deliverylogs"

	// Get all elements from the Redis list
	values, err := db.Rdb.LRange(db.Ctx, key, 0, -1).Result()
	if err != nil {
		return nil, errors.New("failed to read from Redis: " + err.Error())
	}

	for _, v := range values {
		var log models.Deliverylogs
		if err := json.Unmarshal([]byte(v), &log); err != nil {
			continue // skip invalid JSON entries
		}

		// filter only the deliveryid we need
		if log.Deliveryid == did {
			result = append(result, log)
		}
	}

	return result, nil
}

func GetDeliveryLogsv1(deliveryID string) ([]models.Deliverylogs, error) {
	logInfo("GetDeliveryLogsv1", "deliveryid", deliveryID)
	ctx := context.Background()
	var results []models.Deliverylogs

	key := "Deliverylogs:" + deliveryID

	// Fetch logs ONLY for this delivery
	listItems, err := db.Rdb.LRange(ctx, key, 0, -1).Result()
	if err != nil {
		return nil, errors.New("redis lrange error: " + err.Error())
	}

	for _, item := range listItems {

		var log models.Deliverylogs
		if err := json.Unmarshal([]byte(item), &log); err != nil {
			continue
		}

		// Fetch username using redis cached function
		if log.Userid != 0 {
			user, err := GetUserCached(log.Userid)
			if err == nil {
				log.Firstname = user.Firstname
				log.Lastname = user.Lastname
			}
		}

		results = append(results, log)
	}

	return results, nil
}

// func GetDeliveryCached(deliveryID int) (models.Delivery, error) {
//     ctx := context.Background()
//     var data models.Delivery

//     key := fmt.Sprintf("delivery:info:%d", deliveryID)

//     // Check in Redis
//     cached, err := db.Rdb.Get(ctx, key).Result()
//     if err == nil {
//         json.Unmarshal([]byte(cached), &data)
//         return data, nil
//     }

//     // Fetch from DB if not cached
//     q := `
//         SELECT deliveryid, pickupcustomer, deliverycustomer,
//                pickupcontactno, deliverycontactno
//         FROM deliveries
//         WHERE deliveryid=? LIMIT 1
//     `
//     if err := db.DB.Raw(q, deliveryID).Scan(&data).Error; err != nil {
//         return data, err
//     }

//     // Store back to Redis for 6 hours
//     b, _ := json.Marshal(data)
//     db.Rdb.Set(ctx, key, b, 6*time.Hour)

//     return data, nil
// }

func GetUserDeliveryLogs(userid int, fromdate, todate string) ([]models.Deliverylogs, error) {
	logInfo("GetUserDeliveryLogs", "userid", userid)

	var fromTS int64 = 0
	var toTS int64 = time.Now().Unix()

	if fromdate != "" {
		t, err := time.Parse("2006-01-02", fromdate)
		if err != nil {
			return nil, errors.New("invalid fromdate")
		}
		fromTS = t.Unix()
	}

	if todate != "" {
		t, err := time.Parse("2006-01-02", todate)
		if err != nil {
			return nil, errors.New("invalid todate")
		}
		toTS = t.Add(23*time.Hour + 59*time.Minute + 59*time.Second).Unix()
	}

	// 1️⃣ Get delivery IDs for user (O(logN))
	deliveryIDs, err := db.Rdb.ZRangeByScore(
		db.Ctx,
		"user:deliverylogs:"+strconv.Itoa(userid),
		&redis.ZRangeBy{
			Min: strconv.FormatInt(fromTS, 10),
			Max: strconv.FormatInt(toTS, 10),
		},
	).Result()

	if err != nil || len(deliveryIDs) == 0 {
		return []models.Deliverylogs{}, nil
	}

	// 2️⃣ Batch fetch logs
	var result []models.Deliverylogs

	for _, id := range deliveryIDs {
		key := "Deliverylogs:" + id

		values, err := db.Rdb.LRange(db.Ctx, key, 0, -1).Result()
		if err != nil {
			continue
		}

		for _, v := range values {
			var log models.Deliverylogs
			if err := json.Unmarshal([]byte(v), &log); err == nil {
				result = append(result, log)
			}
		}
	}

	return result, nil
}

func logInfo(msg string, props ...interface{}) {
	utils.Logger.Infow(msg, props...)
}

func logError(msg string, props ...interface{}) {
	utils.Logger.Errorw(msg, props...)
}
