package handlers

import (
	"fmt"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/jackc/pgx/v5/pgxpool"
	"ticketing-backend/internal/config"
	"ticketing-backend/internal/models"
	"ticketing-backend/internal/services"
	"ticketing-backend/internal/utils"
)

type AdminHandler struct {
	pool     *pgxpool.Pool
	settings *config.AppSettings
}

func NewAdminHandler(pool *pgxpool.Pool, settings *config.AppSettings) *AdminHandler {
	return &AdminHandler{pool: pool, settings: settings}
}

// ===== Events Management =====

func (h *AdminHandler) ListEvents(c *gin.Context) {
	userID, _ := c.Get("userId")
	role, _ := c.Get("role")
	search := c.Query("search")

	query := `SELECT e.id, e.name, e.venue, e.event_date, e.total_capacity,
		e.remaining_capacity, e.status, e.war_mode, e.created_at
		FROM events e`
	var args []interface{}
	argIdx := 1

	if role.(string) != "super_admin" {
		query += fmt.Sprintf(` WHERE e.organizer_id = $%d`, argIdx)
		args = append(args, userID)
		argIdx++
	}

	if search != "" {
		if argIdx > 1 {
			query += ` AND`
		} else {
			query += ` WHERE`
		}
		query += fmt.Sprintf(` e.name ILIKE $%d`, argIdx)
		args = append(args, "%"+search+"%")
		argIdx++
	}

	query += ` ORDER BY e.event_date DESC`

	rows, err := h.pool.Query(c.Request.Context(), query, args...)
	if err != nil {
		utils.InternalError(c, "Failed to fetch events")
		return
	}
	defer rows.Close()

	type eventRow struct {
		ID                string
		Name              string
		Venue             string
		EventDate         string
		Total             int
		Remaining         int
		Status            string
		WarMode           bool
		CreatedAt         string
	}

	var events []gin.H
	for rows.Next() {
		var r eventRow
		rows.Scan(&r.ID, &r.Name, &r.Venue, &r.EventDate, &r.Total, &r.Remaining, &r.Status, &r.WarMode, &r.CreatedAt)
		events = append(events, gin.H{
			"id": r.ID, "name": r.Name, "venue": r.Venue, "event_date": r.EventDate,
			"total_capacity": r.Total, "remaining_capacity": r.Remaining,
			"status": r.Status, "war_mode": r.WarMode, "created_at": r.CreatedAt,
		})
	}

	utils.Success(c, gin.H{"events": events})
}

func (h *AdminHandler) GetEvent(c *gin.Context) {
	eventID := c.Param("eventId")

	row := h.pool.QueryRow(c.Request.Context(),
		`SELECT e.id, e.name, e.description, e.venue, e.event_date, e.sale_start, e.sale_end,
			e.total_capacity, e.remaining_capacity, e.status, e.war_mode, e.image_url,
			e.queue_max_tickets_per_person, e.queue_session_duration, e.queue_max_active_sessions,
			e.created_at, u.full_name as organizer_name
		 FROM events e
		 LEFT JOIN users u ON u.id = e.organizer_id
		 WHERE e.id = $1`, eventID,
	)

	var e struct {
		ID        string  `json:"id"`
		Name      string  `json:"name"`
		Desc      *string `json:"description"`
		Venue     string  `json:"venue"`
		EventDate string  `json:"event_date"`
		SaleStart string  `json:"sale_start"`
		SaleEnd   string  `json:"sale_end"`
		Total     int     `json:"total_capacity"`
		Remaining int     `json:"remaining_capacity"`
		Status    string  `json:"status"`
		WarMode   bool    `json:"war_mode"`
		ImageURL  *string `json:"image_url"`
		MaxTickets *int   `json:"queue_max_tickets_per_person"`
		SessDur   *int    `json:"queue_session_duration"`
		MaxActive *int    `json:"queue_max_active_sessions"`
		CreatedAt string  `json:"created_at"`
		OrgName   *string `json:"organizer_name"`
	}

	err := row.Scan(&e.ID, &e.Name, &e.Desc, &e.Venue, &e.EventDate, &e.SaleStart, &e.SaleEnd,
		&e.Total, &e.Remaining, &e.Status, &e.WarMode, &e.ImageURL,
		&e.MaxTickets, &e.SessDur, &e.MaxActive, &e.CreatedAt, &e.OrgName)
	if err != nil {
		utils.NotFound(c, "Event not found")
		return
	}

	// Get ticket classes
	tcRows, _ := h.pool.Query(c.Request.Context(),
		`SELECT id, name, description, price, total_capacity, remaining_capacity,
		        sale_window_start, sale_window_end, seat_plan_image, created_at
		 FROM ticket_classes WHERE event_id = $1 ORDER BY price ASC`, eventID)
	defer tcRows.Close()

	var ticketClasses []gin.H
	for tcRows.Next() {
		var id, name string
		var desc, seatPlan *string
		var price float64
		var total, remaining int
		var saleStart, saleEnd, createdAt string
		tcRows.Scan(&id, &name, &desc, &price, &total, &remaining, &saleStart, &saleEnd, &seatPlan, &createdAt)
		ticketClasses = append(ticketClasses, gin.H{
			"id": id, "name": name, "description": desc, "price": price,
			"total_capacity": total, "remaining_capacity": remaining,
			"sale_window_start": saleStart, "sale_window_end": saleEnd,
			"seat_plan_image": seatPlan, "created_at": createdAt,
		})
	}

	// Get event images
	imgRows, _ := h.pool.Query(c.Request.Context(),
		`SELECT id, image_url, display_order FROM event_images WHERE event_id = $1 ORDER BY display_order`, eventID)
	defer imgRows.Close()

	var images []gin.H
	for imgRows.Next() {
		var id, url string
		var order int
		imgRows.Scan(&id, &url, &order)
		images = append(images, gin.H{"id": id, "image_url": url, "display_order": order})
	}

	// Get assigned classes
	classRows, _ := h.pool.Query(c.Request.Context(),
		`SELECT ec.class_id, c.name FROM event_classes ec
		 JOIN classes c ON c.id = ec.class_id WHERE ec.event_id = $1`, eventID)
	defer classRows.Close()

	var assignedClasses []gin.H
	for classRows.Next() {
		var id, name string
		classRows.Scan(&id, &name)
		assignedClasses = append(assignedClasses, gin.H{"id": id, "name": name})
	}

	utils.Success(c, gin.H{
		"event":           e,
		"ticketClasses":   ticketClasses,
		"eventImages":     images,
		"assignedClasses": assignedClasses,
	})
}

func (h *AdminHandler) CreateEvent(c *gin.Context) {
	userID, _ := c.Get("userId")
	var event models.Event
	if err := c.ShouldBindJSON(&event); err != nil {
		utils.BadRequest(c, "Invalid event data")
		return
	}

	var id string
	err := h.pool.QueryRow(c.Request.Context(),
		`INSERT INTO events (organizer_id, name, description, venue, event_date, sale_start, sale_end,
			total_capacity, remaining_capacity, status, war_mode)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
		 RETURNING id`,
		userID, event.Name, event.Description, event.Venue, event.EventDate,
		event.SaleStart, event.SaleEnd, event.TotalCapacity, event.TotalCapacity,
		event.Status, event.WarMode,
	).Scan(&id)
	if err != nil {
		utils.InternalError(c, "Failed to create event")
		return
	}

	utils.Created(c, gin.H{"id": id})
}

func (h *AdminHandler) UpdateEvent(c *gin.Context) {
	eventID := c.Param("eventId")
	var data map[string]interface{}
	if err := c.ShouldBindJSON(&data); err != nil {
		utils.BadRequest(c, "Invalid data")
		return
	}

	sets := ""
	args := []interface{}{}
	idx := 1

	for _, field := range []string{"name", "description", "venue", "event_date", "sale_start", "sale_end",
		"total_capacity", "status", "war_mode", "image_url",
		"queue_max_tickets_per_person", "queue_session_duration", "queue_max_active_sessions"} {
		if val, ok := data[field]; ok {
			if idx > 1 {
				sets += ", "
			}
			sets += fmt.Sprintf("%s = $%d", toSnakeCase(field), idx)
			args = append(args, val)
			idx++
		}
	}

	if idx == 1 {
		utils.BadRequest(c, "No fields to update")
		return
	}

	sets += ", updated_at = NOW()"
	args = append(args, eventID)

	_, err := h.pool.Exec(c.Request.Context(),
		fmt.Sprintf(`UPDATE events SET %s WHERE id = $%d`, sets, idx), args...)
	if err != nil {
		utils.InternalError(c, "Failed to update event")
		return
	}

	utils.Success(c, gin.H{"message": "Event updated"})
}

func (h *AdminHandler) DuplicateEvent(c *gin.Context) {
	eventID := c.Param("eventId")

	var newID string
	err := h.pool.QueryRow(c.Request.Context(),
		`INSERT INTO events (organizer_id, name, description, venue, event_date, sale_start, sale_end,
			total_capacity, remaining_capacity, status, war_mode)
		 SELECT organizer_id, name || ' (Copy)', description, venue, event_date, sale_start, sale_end,
			total_capacity, total_capacity, 'draft', war_mode
		 FROM events WHERE id = $1
		 RETURNING id`, eventID,
	).Scan(&newID)
	if err != nil {
		utils.InternalError(c, "Failed to duplicate event")
		return
	}

	// Copy ticket classes
	_, _ = h.pool.Exec(c.Request.Context(),
		`INSERT INTO ticket_classes (event_id, name, description, price, total_capacity, remaining_capacity,
			sale_window_start, sale_window_end)
		 SELECT $1, name, description, price, total_capacity, total_capacity, sale_window_start, sale_window_end
		 FROM ticket_classes WHERE event_id = $2`, newID, eventID,
	)

	utils.Created(c, gin.H{"id": newID})
}

func (h *AdminHandler) ManageTicketClasses(c *gin.Context) {
	eventID := c.Param("eventId")

	if c.Request.Method == "GET" {
		rows, _ := h.pool.Query(c.Request.Context(),
			`SELECT id, name, description, price, total_capacity, remaining_capacity,
			        sale_window_start, sale_window_end, seat_plan_image, created_at
			 FROM ticket_classes WHERE event_id = $1 ORDER BY price ASC`, eventID)
		defer rows.Close()

		var classes []gin.H
		for rows.Next() {
			var id, name string
			var desc, seatPlan *string
			var price float64
			var total, remaining int
			var sStart, sEnd, createdAt string
			rows.Scan(&id, &name, &desc, &price, &total, &remaining, &sStart, &sEnd, &seatPlan, &createdAt)
			classes = append(classes, gin.H{
				"id": id, "name": name, "description": desc, "price": price,
				"total_capacity": total, "remaining_capacity": remaining,
				"sale_window_start": sStart, "sale_window_end": sEnd,
				"seat_plan_image": seatPlan,
			})
		}

		utils.Success(c, gin.H{"ticketClasses": classes})
		return
	}

	if c.Request.Method == "POST" {
		var tc struct {
			Name            string  `json:"name"`
			Description     *string `json:"description"`
			Price           float64 `json:"price"`
			TotalCapacity   int     `json:"total_capacity"`
			SaleWindowStart string  `json:"sale_window_start"`
			SaleWindowEnd   string  `json:"sale_window_end"`
		}
		if err := c.ShouldBindJSON(&tc); err != nil {
			utils.BadRequest(c, "Invalid ticket class data")
			return
		}

		var id string
		err := h.pool.QueryRow(c.Request.Context(),
			`INSERT INTO ticket_classes (event_id, name, description, price, total_capacity, remaining_capacity,
				sale_window_start, sale_window_end)
			 VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,
			eventID, tc.Name, tc.Description, tc.Price, tc.TotalCapacity, tc.TotalCapacity,
			tc.SaleWindowStart, tc.SaleWindowEnd,
		).Scan(&id)
		if err != nil {
			utils.InternalError(c, "Failed to create ticket class")
			return
		}

		utils.Created(c, gin.H{"id": id})
	}
}

func (h *AdminHandler) UpdateTicketClass(c *gin.Context) {
	eventID := c.Param("eventId")
	tcID := c.Param("ticketClassId")

	var data map[string]interface{}
	if err := c.ShouldBindJSON(&data); err != nil {
		utils.BadRequest(c, "Invalid data")
		return
	}

	sets := ""
	args := []interface{}{}
	idx := 1

	for _, field := range []string{"name", "description", "price", "total_capacity",
		"sale_window_start", "sale_window_end", "seat_plan_image", "remaining_capacity"} {
		if val, ok := data[field]; ok {
			if idx > 1 {
				sets += ", "
			}
			sets += fmt.Sprintf("%s = $%d", toSnakeCase(field), idx)
			args = append(args, val)
			idx++
		}
	}

	if idx == 1 {
		utils.BadRequest(c, "No fields to update")
		return
	}

	sets += ", updated_at = NOW()"
	args = append(args, tcID)

	_, err := h.pool.Exec(c.Request.Context(),
		fmt.Sprintf(`UPDATE ticket_classes SET %s WHERE id = $%d AND event_id = $%d`, sets, idx, idx+1),
		append(args, eventID)...)
	if err != nil {
		utils.InternalError(c, "Failed to update ticket class")
		return
	}

	utils.Success(c, gin.H{"message": "Ticket class updated"})
}

func (h *AdminHandler) DeleteTicketClass(c *gin.Context) {
	tcID := c.Param("ticketClassId")
	_, err := h.pool.Exec(c.Request.Context(),
		`DELETE FROM ticket_classes WHERE id = $1`, tcID)
	if err != nil {
		utils.InternalError(c, "Failed to delete ticket class")
		return
	}
	utils.Success(c, gin.H{"message": "Ticket class deleted"})
}

// ===== Locks Management =====

func (h *AdminHandler) ListLocks(c *gin.Context) {
	eventID := c.Param("eventId")

	rows, _ := h.pool.Query(c.Request.Context(),
		`SELECT ol.id, ol.ticket_class_id, ol.quantity_locked, ol.user_id,
		        u.email, u.full_name, ol.expires_at, ol.status
		 FROM order_locks ol
		 JOIN users u ON u.id = ol.user_id
		 WHERE ol.event_id = $1
		 ORDER BY ol.locked_at DESC`, eventID)
	defer rows.Close()

	var locks []gin.H
	for rows.Next() {
		var id, tcID, userID, email, fName, status string
		var qty int
		var expiresAt string
		rows.Scan(&id, &tcID, &qty, &userID, &email, &fName, &expiresAt, &status)
		locks = append(locks, gin.H{
			"id": id, "ticket_class_id": tcID, "quantity_locked": qty,
			"user_id": userID, "email": email, "full_name": fName,
			"expires_at": expiresAt, "status": status,
		})
	}

	utils.Success(c, gin.H{"locks": locks})
}

func (h *AdminHandler) ReleaseLock(c *gin.Context) {
	lockID := c.Param("lockId")
	_, err := h.pool.Exec(c.Request.Context(),
		`UPDATE order_locks SET status = 'released' WHERE id = $1`, lockID)
	if err != nil {
		utils.InternalError(c, "Failed to release lock")
		return
	}
	utils.Success(c, gin.H{"message": "Lock released"})
}

// ===== Orders Management =====

func (h *AdminHandler) ListOrders(c *gin.Context) {
	userID, _ := c.Get("userId")
	status := c.Query("status")
	search := c.Query("search")

	q := `SELECT o.id, o.event_id, o.buyer_id, o.quantity, o.total_amount,
		o.buyer_name, o.buyer_email, o.status, o.created_at,
		e.name as event_name
		FROM orders o
		JOIN events e ON o.event_id = e.id
		WHERE e.organizer_id = $1`
	args := []interface{}{userID}
	argIdx := 2

	if status != "" {
		q += fmt.Sprintf(` AND o.status = $%d`, argIdx)
		args = append(args, status)
		argIdx++
	}
	if search != "" {
		q += fmt.Sprintf(` AND (o.buyer_name ILIKE $%d OR o.buyer_email ILIKE $%d OR o.id::text ILIKE $%d)`, argIdx, argIdx, argIdx)
		args = append(args, "%"+search+"%")
		argIdx++
	}

	q += ` ORDER BY o.created_at DESC LIMIT 100`

	rows, _ := h.pool.Query(c.Request.Context(), q, args...)
	defer rows.Close()

	type orderRow struct {
		ID, EventID, BuyerID, BuyerName, BuyerEmail, Status, EventName, CreatedAt string
		Quantity  int
		TotalAmount float64
	}

	var orders []gin.H
	for rows.Next() {
		var r orderRow
		rows.Scan(&r.ID, &r.EventID, &r.BuyerID, &r.Quantity, &r.TotalAmount, &r.BuyerName, &r.BuyerEmail, &r.Status, &r.CreatedAt, &r.EventName)
		orders = append(orders, gin.H{
			"id": r.ID, "event_id": r.EventID, "buyer_id": r.BuyerID, "quantity": r.Quantity,
			"total_amount": r.TotalAmount, "buyer_name": r.BuyerName, "buyer_email": r.BuyerEmail,
			"status": r.Status, "created_at": r.CreatedAt, "event_name": r.EventName,
		})
	}

	utils.Success(c, gin.H{"orders": orders})
}

func (h *AdminHandler) GetOrder(c *gin.Context) {
	orderID := c.Param("orderId")

	type orderDetail struct {
		ID, EventID, TcID, BuyerID, BuyerName, BuyerEmail, BuyerPhone, Status, EventName, TcName, CreatedAt string
		Quantity  int
		TotalAmount float64
	}
	var o orderDetail
	err := h.pool.QueryRow(c.Request.Context(),
		`SELECT o.id, o.event_id, o.ticket_class_id, o.buyer_id, o.quantity, o.total_amount,
			o.buyer_name, o.buyer_email, o.buyer_phone, o.status, o.created_at,
			e.name as event_name, tc.name as ticket_class_name
		 FROM orders o
		 JOIN events e ON o.event_id = e.id
		 JOIN ticket_classes tc ON o.ticket_class_id = tc.id
		 WHERE o.id = $1`, orderID,
	).Scan(&o.ID, &o.EventID, &o.TcID, &o.BuyerID, &o.Quantity, &o.TotalAmount,
		&o.BuyerName, &o.BuyerEmail, &o.BuyerPhone, &o.Status, &o.CreatedAt,
		&o.EventName, &o.TcName)
	if err != nil {
		utils.NotFound(c, "Order not found")
		return
	}

	utils.Success(c, gin.H{
		"order": gin.H{
			"id": o.ID, "event_id": o.EventID, "ticket_class_id": o.TcID, "buyer_id": o.BuyerID,
			"quantity": o.Quantity, "total_amount": o.TotalAmount,
			"buyer_name": o.BuyerName, "buyer_email": o.BuyerEmail, "buyer_phone": o.BuyerPhone,
			"status": o.Status, "created_at": o.CreatedAt,
			"event_name": o.EventName, "ticket_class_name": o.TcName,
		},
	})
}

// ===== Attendees =====

func (h *AdminHandler) ListAttendees(c *gin.Context) {
	eventID := c.Param("eventId")

	rows, _ := h.pool.Query(c.Request.Context(),
		`SELECT t.id, t.booking_code, t.ticket_class_name, t.buyer_name, t.buyer_email,
		        t.status, t.used_at, t.created_at
		 FROM tickets t
		 JOIN orders o ON t.order_id = o.id
		 WHERE o.event_id = $1
		 ORDER BY t.created_at DESC`, eventID)
	defer rows.Close()

	type attendeeRow struct {
		ID, BookingCode, TcName, BuyerName, BuyerEmail, Status, CreatedAt string
		UsedAt *string
	}
	var attendees []gin.H
	for rows.Next() {
		var r attendeeRow
		rows.Scan(&r.ID, &r.BookingCode, &r.TcName, &r.BuyerName, &r.BuyerEmail, &r.Status, &r.UsedAt, &r.CreatedAt)
		attendees = append(attendees, gin.H{
			"id": r.ID, "booking_code": r.BookingCode, "ticket_class_name": r.TcName,
			"buyer_name": r.BuyerName, "buyer_email": r.BuyerEmail, "status": r.Status,
			"used_at": r.UsedAt, "created_at": r.CreatedAt,
		})
	}

	utils.Success(c, gin.H{"attendees": attendees})
}

// ===== Promos Management =====

func (h *AdminHandler) ListPromos(c *gin.Context) {
	var promos []interface{}

	rows, _ := h.pool.Query(c.Request.Context(),
		`SELECT p.id, p.code, p.name, p.promo_type, p.value, p.min_purchase,
		        p.max_discount, p.usage_limit, p.usage_count, p.per_user_limit,
		        p.valid_from, p.valid_until, p.is_active, p.created_at,
		        e.name as event_name
		 FROM promos p LEFT JOIN events e ON p.event_id = e.id
		 ORDER BY p.created_at DESC`)
	defer rows.Close()

	for rows.Next() {
		var id, code, name, pType, validFrom, validUntil, createdAt string
		var value, minP float64
		var maxD *float64
		var usageL *int
		var usageC, perUL int
		var isActive bool
		var eName *string
		rows.Scan(&id, &code, &name, &pType, &value, &minP, &maxD, &usageL, &usageC, &perUL,
			&validFrom, &validUntil, &isActive, &createdAt, &eName)
		promos = append(promos, gin.H{
			"id": id, "code": code, "name": name, "promo_type": pType, "value": value,
			"min_purchase": minP, "max_discount": maxD, "usage_limit": usageL,
			"usage_count": usageC, "per_user_limit": perUL,
			"valid_from": validFrom, "valid_until": validUntil, "is_active": isActive,
			"created_at": createdAt, "event_name": eName,
		})
	}

	utils.Success(c, gin.H{"promos": promos})
}

func (h *AdminHandler) CreatePromo(c *gin.Context) {
	userID, _ := c.Get("userId")
	userIDStr, _ := userID.(string)

	var data map[string]interface{}
	if err := c.ShouldBindJSON(&data); err != nil {
		utils.BadRequest(c, "Invalid promo data")
		return
	}

	promo, err := services.NewPromoService(h.pool).CreatePromo(data, userIDStr)
	if err != nil {
		utils.InternalError(c, "Failed to create promo")
		return
	}

	utils.Created(c, gin.H{"promo": promo})
}

func (h *AdminHandler) UpdatePromo(c *gin.Context) {
	promoID := c.Param("promoId")
	var data map[string]interface{}
	if err := c.ShouldBindJSON(&data); err != nil {
		utils.BadRequest(c, "Invalid data")
		return
	}

	sets := ""
	args := []interface{}{}
	idx := 1

	for _, field := range []string{"code", "name", "description", "promo_type", "value",
		"min_purchase", "max_discount", "usage_limit", "per_user_limit", "event_id",
		"valid_from", "valid_until", "is_active"} {
		if val, ok := data[field]; ok {
			if idx > 1 {
				sets += ", "
			}
			sets += fmt.Sprintf("%s = $%d", toSnakeCase(field), idx)
			args = append(args, val)
			idx++
		}
	}

	if idx == 1 {
		utils.BadRequest(c, "No fields to update")
		return
	}

	sets += ", updated_at = NOW()"
	args = append(args, promoID)

	_, err := h.pool.Exec(c.Request.Context(),
		fmt.Sprintf(`UPDATE promos SET %s WHERE id = $%d`, sets, idx), args...)
	if err != nil {
		utils.InternalError(c, "Failed to update promo")
		return
	}

	utils.Success(c, gin.H{"message": "Promo updated"})
}

func (h *AdminHandler) DeletePromo(c *gin.Context) {
	promoID := c.Param("promoId")
	_, err := h.pool.Exec(c.Request.Context(), `DELETE FROM promos WHERE id = $1`, promoID)
	if err != nil {
		utils.InternalError(c, "Failed to delete promo")
		return
	}
	utils.Success(c, gin.H{"message": "Promo deleted"})
}

// ===== Domain Promos =====

func (h *AdminHandler) ListDomainPromos(c *gin.Context) {
	rows, _ := h.pool.Query(c.Request.Context(),
		`SELECT dp.*, e.name as event_name FROM domain_promos dp
		 LEFT JOIN events e ON dp.event_id = e.id ORDER BY dp.created_at DESC`)
	defer rows.Close()

	var promos []gin.H
	for rows.Next() {
		var id, domain, pType string
		var value float64
		var maxD *float64
		var eID, desc, eName *string
		var isActive bool
		var createdBy, createdAt, updatedAt string
		rows.Scan(&id, &domain, &pType, &value, &maxD, &eID, &desc, &isActive, &createdBy, &createdAt, &updatedAt, &eName)
		promos = append(promos, gin.H{
			"id": id, "domain": domain, "promo_type": pType, "value": value,
			"max_discount": maxD, "event_id": eID, "description": desc,
			"is_active": isActive, "created_at": createdAt, "event_name": eName,
		})
	}

	utils.Success(c, gin.H{"domainPromos": promos})
}

func (h *AdminHandler) CreateDomainPromo(c *gin.Context) {
	userID, _ := c.Get("userId")
	var data map[string]interface{}
	if err := c.ShouldBindJSON(&data); err != nil {
		utils.BadRequest(c, "Invalid data")
		return
	}

	domain, _ := data["domain"].(string)
	sPromoType, _ := data["promoType"].(string)
	value, _ := data["value"].(float64)

	var id string
	err := h.pool.QueryRow(c.Request.Context(),
		`INSERT INTO domain_promos (domain, promo_type, value, max_discount, event_id, description, created_by)
		 VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
		domain, sPromoType, value, float64Ptr(data["maxDiscount"]),
		strPtrPtr(data["eventId"]), strPtrPtr(data["description"]), userID,
	).Scan(&id)
	if err != nil {
		utils.InternalError(c, "Failed to create domain promo")
		return
	}

	utils.Created(c, gin.H{"id": id})
}

// ===== Tickets =====

func (h *AdminHandler) ListTickets(c *gin.Context) {
	rows, _ := h.pool.Query(c.Request.Context(),
		`SELECT t.id, t.booking_code, t.ticket_class_name, t.buyer_name, t.buyer_email,
		        t.event_name, t.event_date, t.status, t.used_at, t.created_at
		 FROM tickets t ORDER BY t.created_at DESC LIMIT 200`)
	defer rows.Close()

	type ticketRow struct {
		ID, BookingCode, TcName, BuyerName, BuyerEmail, EventName, Status, CreatedAt string
		EventDate string
		UsedAt *string
	}
	var tickets []gin.H
	for rows.Next() {
		var r ticketRow
		rows.Scan(&r.ID, &r.BookingCode, &r.TcName, &r.BuyerName, &r.BuyerEmail, &r.EventName, &r.EventDate, &r.Status, &r.UsedAt, &r.CreatedAt)
		tickets = append(tickets, gin.H{
			"id": r.ID, "booking_code": r.BookingCode, "ticket_class_name": r.TcName,
			"buyer_name": r.BuyerName, "buyer_email": r.BuyerEmail, "event_name": r.EventName,
			"event_date": r.EventDate, "status": r.Status, "used_at": r.UsedAt, "created_at": r.CreatedAt,
		})
	}

	utils.Success(c, gin.H{"tickets": tickets})
}

// ===== Payments =====

func (h *AdminHandler) ListPayments(c *gin.Context) {
	rows, _ := h.pool.Query(c.Request.Context(),
		`SELECT p.id, p.order_id, p.amount, p.gateway, p.gateway_transaction_id,
		        p.status, p.created_at
		 FROM payments p ORDER BY p.created_at DESC LIMIT 100`)
	defer rows.Close()

	type paymentRow struct {
		ID, OrderID, Gateway, Status, CreatedAt string
		Amount float64
		GtxID *string
	}
	var payments []gin.H
	for rows.Next() {
		var r paymentRow
		rows.Scan(&r.ID, &r.OrderID, &r.Amount, &r.Gateway, &r.GtxID, &r.Status, &r.CreatedAt)
		payments = append(payments, gin.H{
			"id": r.ID, "order_id": r.OrderID, "amount": r.Amount, "gateway": r.Gateway,
			"gateway_transaction_id": r.GtxID, "status": r.Status, "created_at": r.CreatedAt,
		})
	}

	utils.Success(c, gin.H{"payments": payments})
}

// ===== Users =====

func (h *AdminHandler) ListUsers(c *gin.Context) {
	rows, _ := h.pool.Query(c.Request.Context(),
		`SELECT id, email, full_name, role, is_verified, created_at
		 FROM users ORDER BY created_at DESC LIMIT 200`)
	defer rows.Close()

	type userRow struct {
		ID, Email, FullName, Role, CreatedAt string
		IsVerified bool
	}
	var users []gin.H
	for rows.Next() {
		var r userRow
		rows.Scan(&r.ID, &r.Email, &r.FullName, &r.Role, &r.IsVerified, &r.CreatedAt)
		users = append(users, gin.H{
			"id": r.ID, "email": r.Email, "full_name": r.FullName, "role": r.Role,
			"is_verified": r.IsVerified, "created_at": r.CreatedAt,
		})
	}

	utils.Success(c, gin.H{"users": users})
}

func (h *AdminHandler) GetUser(c *gin.Context) {
	userID := c.Param("userId")

	type userRow struct {
		ID, Email, FullName, Role, CreatedAt string
		IsVerified bool
		ClassID *string
	}
	var u userRow
	err := h.pool.QueryRow(c.Request.Context(),
		`SELECT id, email, full_name, role, is_verified, class_id, created_at
		 FROM users WHERE id = $1`, userID,
	).Scan(&u.ID, &u.Email, &u.FullName, &u.Role, &u.IsVerified, &u.ClassID, &u.CreatedAt)
	if err != nil {
		utils.NotFound(c, "User not found")
		return
	}

	utils.Success(c, gin.H{
		"user": gin.H{
			"id": u.ID, "email": u.Email, "full_name": u.FullName, "role": u.Role,
			"is_verified": u.IsVerified, "class_id": u.ClassID, "created_at": u.CreatedAt,
		},
	})
}

// ===== Reports =====

func (h *AdminHandler) GetReports(c *gin.Context) {
	var totalEvents, totalOrders int
	var totalRevenueFloat float64
	h.pool.QueryRow(c.Request.Context(), `SELECT COUNT(*) FROM events`).Scan(&totalEvents)
	h.pool.QueryRow(c.Request.Context(), `SELECT COUNT(*) FROM orders WHERE status = 'completed'`).Scan(&totalOrders)
	h.pool.QueryRow(c.Request.Context(), `SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE status = 'completed'`).Scan(&totalRevenueFloat)

	utils.Success(c, gin.H{
		"totalEvents":  totalEvents,
		"totalOrders":  totalOrders,
		"totalRevenue": totalRevenueFloat,
	})
}

// ===== Settings (now reads from config.json) =====

func (h *AdminHandler) GetSettings(c *gin.Context) {
	// Read current config from file
	data, err := os.ReadFile("config.json")
	if err != nil {
		utils.InternalError(c, "Failed to read config")
		return
	}

	var rawSettings map[string]interface{}
	if err := utils.JSONParse(data, &rawSettings); err != nil {
		utils.InternalError(c, "Failed to parse config")
		return
	}

	utils.Success(c, gin.H{"settings": rawSettings})
}

func (h *AdminHandler) UpdateSettings(c *gin.Context) {
	var incoming map[string]interface{}
	if err := c.ShouldBindJSON(&incoming); err != nil {
		utils.BadRequest(c, "Invalid data")
		return
	}

	// Read existing config
	data, err := os.ReadFile("config.json")
	if err != nil {
		utils.InternalError(c, "Failed to read config")
		return
	}

	var current map[string]interface{}
	if err := utils.JSONParse(data, &current); err != nil {
		utils.InternalError(c, "Failed to parse config")
		return
	}

	// Deep-merge incoming into current
	deepMerge(current, incoming)

	// Write back
	updated, err := utils.JSONMarshal(current)
	if err != nil {
		utils.InternalError(c, "Failed to marshal config")
		return
	}

	if err := os.WriteFile("config.json", updated, 0644); err != nil {
		utils.InternalError(c, "Failed to write config")
		return
	}

	utils.Success(c, gin.H{"message": "Settings updated. Restart required for some changes to take effect."})
}

// ===== All Attendees =====

func (h *AdminHandler) ListAllAttendees(c *gin.Context) {
	search := c.Query("search")

	q := `SELECT t.id, t.booking_code, t.ticket_class_name, t.buyer_name, t.buyer_email,
		t.event_name, t.event_date, t.status, t.used_at, t.created_at
		FROM tickets t`
	var args []interface{}
	argIdx := 1

	if search != "" {
		q += fmt.Sprintf(` WHERE (t.buyer_name ILIKE $%d OR t.buyer_email ILIKE $%d OR t.booking_code ILIKE $%d)`, argIdx, argIdx, argIdx)
		args = append(args, "%"+search+"%")
		argIdx++
	}

	q += ` ORDER BY t.created_at DESC LIMIT 200`

	rows, _ := h.pool.Query(c.Request.Context(), q, args...)
	defer rows.Close()

	type attendeeRow struct {
		ID, BookingCode, TcName, BuyerName, BuyerEmail, EventName, Status, CreatedAt string
		EventDate string
		UsedAt *string
	}
	var attendees []gin.H
	for rows.Next() {
		var r attendeeRow
		rows.Scan(&r.ID, &r.BookingCode, &r.TcName, &r.BuyerName, &r.BuyerEmail, &r.EventName, &r.EventDate, &r.Status, &r.UsedAt, &r.CreatedAt)
		attendees = append(attendees, gin.H{
			"id": r.ID, "booking_code": r.BookingCode, "ticket_class_name": r.TcName,
			"buyer_name": r.BuyerName, "buyer_email": r.BuyerEmail, "event_name": r.EventName,
			"event_date": r.EventDate, "status": r.Status, "used_at": r.UsedAt, "created_at": r.CreatedAt,
		})
	}

	utils.Success(c, gin.H{"attendees": attendees})
}

// Helpers

func toSnakeCase(s string) string {
	result := ""
	for i, c := range s {
		if c >= 'A' && c <= 'Z' {
			if i > 0 {
				result += "_"
			}
			result += string(c + 32)
		} else {
			result += string(c)
		}
	}
	return result
}

func float64Ptr(v interface{}) *float64 {
	if v == nil {
		return nil
	}
	switch val := v.(type) {
	case float64:
		return &val
	case string:
		if val == "" {
			return nil
		}
		var f float64
		fmt.Sscanf(val, "%f", &f)
		return &f
	}
	return nil
}

func strPtrPtr(v interface{}) *string {
	if v == nil {
		return nil
	}
	switch val := v.(type) {
	case string:
		if val == "" {
			return nil
		}
		return &val
	}
	return nil
}

func deepMerge(dst, src map[string]interface{}) {
	for k, sv := range src {
		if dv, ok := dst[k]; ok {
			dvMap, dvOk := dv.(map[string]interface{})
			svMap, svOk := sv.(map[string]interface{})
			if dvOk && svOk {
				deepMerge(dvMap, svMap)
				continue
			}
		}
		dst[k] = sv
	}
}
