package main

import (
	"database/sql"
	"log"
	"net/http"
	"strconv"
	"time"
	_ "time/tzdata"
	"crypto/aes"
	"crypto/cipher"
	// "crypto/rand"
	"encoding/base64"
	"bytes"       // For byte manipulation (e.g., PKCS7 padding)
	"fmt"         // For formatted I/O (e.g., error messages)
  "context"
  // "os"
  // "os/signal"
  // "syscall"
	"errors"
	"math/rand"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"
	"io"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/dgrijalva/jwt-go"
	_ "github.com/go-sql-driver/mysql"
  // "github.com/skip2/go-qrcode" // Import the QR code library
  "go.mau.fi/whatsmeow"
  "go.mau.fi/whatsmeow/store/sqlstore"
  "go.mau.fi/whatsmeow/types"
  // "go.mau.fi/whatsmeow/types/events"
  _ "github.com/mattn/go-sqlite3" // Im  the SQLite driver
	"github.com/skip2/go-qrcode"
	"sync"
	"go.mau.fi/whatsmeow/types/events"
	waLog "go.mau.fi/whatsmeow/util/log"


	"regexp"
	waE2E "go.mau.fi/whatsmeow/binary/proto"
)

var (
	whatsappClient    *whatsmeow.Client
	whatsappContainer *sqlstore.Container
	clientMutex       sync.RWMutex
)

// WhatsApp-specific types
type WhatsAppAPI struct {
	client *whatsmeow.Client
}

type LoginResponse struct {
	Status string `json:"status"`
	QR     string `json:"qr,omitempty"`
	Error  string `json:"error,omitempty"`
}

type SendRequest struct {
	To      string `json:"to" binding:"required"`
	Message string `json:"message" binding:"required"`
}

type SendResponse struct {
	Status  string `json:"status"`
	Message string `json:"message,omitempty"`
	Error   string `json:"error,omitempty"`
}

type StatusResponse struct {
	Connected bool `json:"connected"`
	LoggedIn  bool `json:"logged_in"`
}

type Product struct {
	KodeBrg      		string  				`json:"kodeBrg"`
	NamaBrg      		string  				`json:"namaBrg"`
	Harga        		*string  				`json:"harga"`
	Berat        		*string  				`json:"berat"`
	Limit        		*string  				`json:"limit"`
	HargaPromo   		*string  				`json:"hargaPromo"`
	ImageURL     		*string  				`json:"imageUrl"`
	ImageKitURL			sql.NullString	`json:"imageKitUrl"`
	KodeGolongan 		*string  				`json:"kodeGolongan"`
	Kategori     		*string  				`json:"kategori"`
	KodePromo    		*string 				`json:"kodePromo"`
	Deskripsi    		*string  				`json:"deskripsi"`
	Stock        		*string  				`json:"stock"`
	Prioritas    		*string 				`json:"prioritas"`
	BonusItem    		*string  				`json:"bonusItem"`
	JenisPotongan 	*string 				`json:"jenisPotongan"`
	JumlahPotongan  *int					 	`json:"jumlahPotongan"`
	MinQtyGrosir		*int						`json:"minQtyGrosir"`
}

type Customer struct {
	Username  string `json:"username"`
	Password  string `json:"password"`
	Email     string `json:"email"`
	TipeUser  string `json:"tipeUser"`
	Name      string `json:"name"`
	Address   string `json:"address"`
	Regency	  string `json:"regency"`
	District  string `json:"district"`
	Village  	string `json:"village"`
	Phone     string `json:"phone"`
	LastLogin string `json:"lastLogin"`
}

type Order struct {
	KodeOrder   string      `json:"kode_order,omitempty"` // Optional; generated later
	Tanggal     string      `json:"tanggal,omitempty"`  // Optional; set later
	NamaCabang  string      `json:"nama_cabang"`
	Username    string      `json:"username"`
	NamaCust    string      `json:"nama_cust"`
	AlamatCust  string      `json:"alamat_cust"`
	Lat         string      `json:"lat"`
	Long        string      `json:"long"`
	Nilai       float64     `json:"nilai"`
	Discount    *float64    `json:"discount"`
	StatusOrder string      `json:"status_order,omitempty"` // Optional; default to "proses"
	BatalOleh   *string     `json:"batalOleh,omitempty"`
	KetBatal    *string     `json:"ketBatal,omitempty"`
	Ongkir      float64     `json:"ongkir"`
	Versi       string      `json:"versi"`
	CaraBayar   *string      `json:"cara_bayar"`
	KodeCabang	string			`json:"kode_cabang"`
	KodeVoucher	string			`json:"kode_voucher"`
	Items       []OrderItem `json:"items"`
}

type OrderItem struct {
	KodeOrder   string  `json:"kode_order,omitempty"` // Optional; generated later
	KodeBarang  string  `json:"kode_barang"`
	NamaBarang  string  `json:"nama_barang"`
	ImageURL    string  `json:"image_url"`
	Jumlah      int     `json:"jumlah"`
	Harga       float64 `json:"harga,string"`
	HargaPromo  float64 `json:"harga_promo,string"`
	IsFree      string  `json:"is_free"`
	Berat       float64 `json:"berat,string"`
	HargaJual   float64 `json:"harga_jual,string"`
	Promo       string  `json:"promo"`
}

type Banner struct {
	ImageURL string `json:"url"`
}

type Info struct {
	ID         int      `json:"id_info"`
	NamaInfo   string   `json:"nama_info"`
	KodeInfo   string   `json:"kode_info"`
	Keterangan string   `json:"keterangan"`
	Mode       InfoMode `json:"mode"`
}

type InfoMode struct {
	Type    string `json:"type"`
	Payload string `json:"payload"`
}

type Voucher struct {
	KodeVoucher  		string `json:"kodeVoucher"`
	StartDate  			string `json:"startDate"`
	EndDate  				string `json:"endDate"`
	TargetVoucher		string `json:"targetVoucher"`
	IdTarget      	string `json:"idTarget"`
	JenisPotongan 	string `json:"jenisPotongan"`
	JumlahPotongan  int `json:"jumlahPotongan"`
	IsDoubleDisc  *int `json:"isDoubleDisc"`
}

type Cabang struct {
	NamaCabang  	*string 	`json:"nama_cabang"`
	KodeCabang  	*string 	`json:"kode_cabang"`
	Lat  					*string 	`json:"lat"`
	Long					*string 	`json:"long"`
	Keterangan  	*string 	`json:"keterangan"`
	Persen				float64		`json:"persen"`
	Aktif 				*string 	`json:"aktif"`
}

type Setup struct {
	Group		 string  `json:"group"`
	Subgroup string  `json:"subgroup"`
	Nilai    int		 `json:"nilai"`
}

type Kabupaten struct {
	Id			 		int		  `json:"id"`
	ProvinceId 	int		  `json:"province_id"`
	Nama	    	string	`json:"name"`
}

type Kecamatan struct {
	Id			 		int		  `json:"id"`
	RegencyId 	int		  `json:"regency_id"`
	Nama	    	string	`json:"name"`
}

type Desa struct {
	Id			 		int		  `json:"id"`
	DistrictId 	int		  `json:"district_id"`
	Nama	    	string	`json:"name"`
}

var db *sql.DB
var jwtSecret = []byte("your_secret_key")

// Key for AES-256 encryption (must be 32 bytes for AES-256)
var encryptionKey = []byte("MR5EPvupatnIMbDq3Ix178RAZbYzF94A") // 32 bytes key for AES-256

// AES encryption
func encryptAES256CBC(plainText string) (string, error) {
	block, err := aes.NewCipher(encryptionKey)
	if err != nil {
		return "", err
	}

	// Generate random IV (16 bytes for AES block size)
	iv := make([]byte, aes.BlockSize)
	if _, err := rand.Read(iv); err != nil {
		return "", err
	}

	// Pad the plaintext using PKCS7
	padding := aes.BlockSize - len(plainText)%aes.BlockSize
	paddedText := append([]byte(plainText), bytes.Repeat([]byte{byte(padding)}, padding)...)

	// Encrypt the plaintext
	ciphertext := make([]byte, len(paddedText))
	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext, paddedText)

	// Combine IV and ciphertext and encode to Base64
	ivAndCiphertext := append(iv, ciphertext...)
	return base64.StdEncoding.EncodeToString(ivAndCiphertext), nil
}

// AES decryption
func decryptAES256CBC(cipherTextBase64 string) (string, error) {
	block, err := aes.NewCipher(encryptionKey)
	if err != nil {
		return "", err
	}

	// Decode Base64 string
	cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
	if err != nil {
		return "", err
	}

	// Extract IV and ciphertext
	if len(cipherText) < aes.BlockSize {
		return "", fmt.Errorf("ciphertext too short")
	}
	iv := cipherText[:aes.BlockSize]
	cipherText = cipherText[aes.BlockSize:]

	// Decrypt the ciphertext
	plainText := make([]byte, len(cipherText))
	mode := cipher.NewCBCDecrypter(block, iv)
	mode.CryptBlocks(plainText, cipherText)

	// Remove PKCS7 padding
	padding := int(plainText[len(plainText)-1])
	if padding < 1 || padding > aes.BlockSize {
		return "", fmt.Errorf("invalid padding")
	}
	for i := len(plainText) - padding; i < len(plainText); i++ {
		if plainText[i] != byte(padding) {
			return "", fmt.Errorf("invalid padding")
		}
	}
	plainText = plainText[:len(plainText)-padding]

	return string(plainText), nil
}

// generateASCIIQR creates an ASCII QR code from the given text
func generateASCIIQR(text string) (string, error) {
	qr, err := qrcode.New(text, qrcode.Medium)
	if err != nil {
		return "", fmt.Errorf("failed to generate QR code: %v", err)
	}
	ascii := qr.ToSmallString(false) // false for no inverted colors
	return ascii, nil
}

// normalizePhoneNumber converts various Indonesian number formats to 628xxxxxxxxxx
func normalizePhoneNumber(phone string) (string, error) {
	// Remove all non-digits (spaces, dashes, etc.)
	re := regexp.MustCompile(`\D`)
	cleaned := re.ReplaceAllString(phone, "")

	// Handle different prefixes
	if strings.HasPrefix(cleaned, "62") {
		return cleaned, nil
	} else if strings.HasPrefix(cleaned, "+62") {
		return strings.TrimPrefix(cleaned, "+"), nil
	} else if strings.HasPrefix(cleaned, "08") {
		return "62" + cleaned[1:], nil
	}

	// Validate length (10-13 digits, typically 12 for Indonesian mobile)
	if len(cleaned) < 10 || len(cleaned) > 13 {
		return "", fmt.Errorf("invalid phone number length: %s", phone)
	}

	return cleaned, nil
}

// Initialize WhatsApp client
func initWhatsAppClient() error {
	// Setup logger
	logger := waLog.Stdout("wa", "INFO", true)

	// Setup SQL store with logger
	container, err := sqlstore.New(context.Background(), "sqlite3", "whatsmeow.db?_foreign_keys=on", logger)
	if err != nil {
		return fmt.Errorf("failed to create SQL store: %v", err)
	}
	whatsappContainer = container

	// Get or create device
	device, err := container.GetFirstDevice(context.Background())
	if err != nil {
		return fmt.Errorf("failed to get device: %v", err)
	}

	// Create client with logger and auto-reconnect
	client := whatsmeow.NewClient(device, logger)
	client.EnableAutoReconnect = true
	client.AddEventHandler(func(evt interface{}) {
		switch evt.(type) {
		case *events.Message:
			// Handle incoming messages if needed
		}
	})

	whatsappClient = client
	return nil
}

// Get WhatsApp client
func getWhatsAppClient() (*whatsmeow.Client, error) {
	clientMutex.RLock()
	client := whatsappClient
	clientMutex.RUnlock()

	if client == nil {
		return nil, errors.New("WhatsApp client not initialized")
	}
	return client, nil
}

func main() {

	var err error
	// Connect to MySQL
	db, err = sql.Open("mysql", "client:@tcp(server.linksoftindo.com:3308)/dbbayiku?parseTime=true&multiStatements=true&allowOldPasswords=1&loc="+url.QueryEscape("Asia/Jakarta"))
	// rabasuni11A
	// sql.Open("mysql", "client:@tcp(server.linksoftindo.com:3308)/dbbayiku?parseTime=true&allowOldPasswords=1&multiStatements=true&loc=" + url.QueryEscape("Asia/Bangkok"))
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// Test the connection
	if err := db.Ping(); err != nil {
		log.Fatal("Database connection failed:", err)
	}

	r := gin.Default()

	// Add CORS middleware globally to allow cross-origin requests
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"*"}, // Allow all origins (replace with specific origins if needed)
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
		AllowCredentials: true,
	}))

	// Initialize WhatsApp client
  if err := initWhatsAppClient(); err != nil {
      log.Printf("Warning: Failed to initialize WhatsApp client: %v", err)
  }

	// Serve static images from the /images/ directory
	r.Static("/images", "./images") // Assuming your images are in a directory named "images" at the root of your project

	// Public Routes
	r.POST("/auth/login", loginHandler)
	r.POST("/auth/register", registerHandler)
	// Add the new OTP route
	r.POST("/send-otp", sendOTPHandler)
	r.POST("/send-otp-forgot", sendOTPForgotHandler)
	r.POST("/verify-otp", verifyOTPHandler)
	r.POST("/verify-otp-forgot", verifyForgotOTPHandler)
	r.GET("/cek-time", getCurrentTimeHandler)
	// Address
	r.POST("/regency", regencyHandler)
	r.POST("/district/:regency_id", districtHandler)
	r.POST("/village/:district_id", villageHandler)

	// WhatsApp routes (from working code)
	r.GET("/whatsapp/login", whatsappLoginHandler)
	r.POST("/whatsapp/send", whatsappSendHandler)
	r.GET("/whatsapp/status", whatsappStatusHandler)
	r.POST("/whatsapp/logout", whatsappLogoutHandler)
	// Protected Routes
	protected := r.Group("/products")
	// protected.Use(jwtAuthMiddleware())
	protected.GET("/", getProducts)
	protected.GET("/grosir", getGrosirProducts)
	protected.POST("/address", getAdministrativeData)
	protected.GET("/home", getHomeProducts)
	protected.GET("/promo", getPromoProducts)
	protected.GET("/:id", getProductByID)
	protected.GET("/grosir/:id", getProductGrosirByID)
	protected.GET("/golongan/:id", getProductByIDGolongan)
	protected.GET("/grosir/golongan/:id", getProductGrosirByIDGolongan)
	// protected.POST("/", createProduct)
	// protected.PUT("/:id", updateProduct)
	// protected.DELETE("/:id", deleteProduct)

	protectedAuth := r.Group("/auth")
	protectedAuth.Use(jwtAuthMiddleware())
	protectedAuth.POST("/password", changePasswordHandler)
	protectedAuth.POST("/forgot-change-password", changePassworForgotdHandler)
	protectedAuth.POST("/profile", updateProfileHandler)
	protectedAuth.GET("/profile/:username", getProfileHandler)

	protectedOrders := r.Group("/orders")
	protectedOrders.Use(jwtAuthMiddleware())
	protectedOrders.GET("/show", showOrdersHandler)
	protectedOrders.POST("/insert", insertOrderHandler)
	protectedOrders.POST("/batal", batalOrderHandler)

	protectedSettings := r.Group("/settings")
	protectedSettings.Use(jwtAuthMiddleware())
	protectedSettings.GET("/banner", getBannerHandler)
	protectedSettings.GET("/info", getInfoHandler)

	protectedVoucher := r.Group("/voucher")
	protectedVoucher.Use(jwtAuthMiddleware())
	protectedVoucher.GET("/", getVoucher)
	protectedVoucher.GET("/code/:code", getVoucherCode)
	protectedVoucher.POST("/", createVoucher)

	protectedCabang := r.Group("/cabang")
	protectedCabang.Use(jwtAuthMiddleware())
	protectedCabang.GET("/", getCabang)

	// Add the new setup route
	protectedSetup := r.Group("/setup")
	protectedSetup.Use(jwtAuthMiddleware())
	protectedSetup.GET("/ongkir", getOngkirHandler)
  // r.GET("/generate-token", func(c *gin.Context) {
  //     token, err := generateToken("admin") // Replace "admin" with any username
  //     if err != nil {
  //         c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
  //         return
  //     }
  //     c.JSON(http.StatusOK, gin.H{"token": token})
  // })

	// Run the server
	r.Run(":9797")
}

// JWT utilities
func generateToken(username string) (string, error) {
	claims := jwt.MapClaims{
		"username": username,
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtSecret)
}

func jwtAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization token required"})
			c.Abort()
			return
		}

		tokenString := authHeader[len("Bearer "):]
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, gin.Error{Err: jwt.ErrSignatureInvalid}
			}
			return jwtSecret, nil
		})

		if err != nil || !token.Valid {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
			c.Abort()
			return
		}

		// Add username to context
		claims := token.Claims.(jwt.MapClaims)
		c.Set("username", claims["username"])
		c.Next()
	}
}

// Auth Handlers
// Auth Handlers
func loginHandler(c *gin.Context) {
	var loginData struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}

	if err := c.ShouldBindJSON(&loginData); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
		return
	}

	var customer Customer
	err := db.QueryRow("SELECT username,password, nama, alamat,kabupaten,kecamatan,desa,no_telp,email,tipe_user FROM tb_customer WHERE username = ?", loginData.Username).Scan(&customer.Username, &customer.Password, &customer.Name, &customer.Address, &customer.Regency, &customer.District, &customer.Village, &customer.Phone, &customer.Email, &customer.TipeUser)
	if err == sql.ErrNoRows {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
		return
	}

	// Decrypt the stored password (encrypted)
	decryptedPassword, err := decryptAES256CBC(customer.Password)
	if err != nil || decryptedPassword != loginData.Password {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password", "decryptedPassword": decryptedPassword, "inputPassword": loginData.Password})
		return
	}

	token, err := generateToken(customer.Username)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"token": token, "data": customer, "status": http.StatusOK})
}

func registerHandler(c *gin.Context) {
	var newCustomer Customer
	if err := c.ShouldBindJSON(&newCustomer); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
		return
	}

	// Encrypt the password before storing
	encryptedPassword, err := encryptAES256CBC(newCustomer.Password)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to encrypt password"})
		return
	}

	_, err = db.Exec("INSERT INTO tb_customer (username, password, email, nama, alamat, kabupaten, kecamatan, desa, no_telp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
		newCustomer.Username, encryptedPassword, newCustomer.Email, newCustomer.Name, newCustomer.Address, newCustomer.Regency, newCustomer.District, newCustomer.Village, newCustomer.Phone)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err})
		return
	}

	c.JSON(http.StatusCreated, gin.H{"message": "Registration successful"})
}

func changePasswordHandler(c *gin.Context) {
    username := c.GetString("username") // Get the logged-in user's username

    var passwordData struct {
        OldPassword string `json:"password_lama"`
        NewPassword string `json:"password_baru"`
    }

    if err := c.ShouldBindJSON(&passwordData); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
        return
    }

    // Fetch the encrypted password from the database for the logged-in user
    var encryptedPassword string
    err := db.QueryRow("SELECT password FROM tb_customer WHERE username = ?", username).Scan(&encryptedPassword)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve user information"})
        return
    }

    // Decrypt the old password from the database
    decryptedOldPassword, err := decryptAES256CBC(encryptedPassword)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to decrypt old password"})
        return
    }

    // Compare the decrypted old password with the provided old password
    if decryptedOldPassword != passwordData.OldPassword {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Incorrect old password", "old": passwordData.OldPassword, "decrypted": decryptedOldPassword})
        return
    }

    // Encrypt the new password before storing it
    encryptedNewPassword, err := encryptAES256CBC(passwordData.NewPassword)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to encrypt new password"})
        return
    }

    // Update the password in the database
    _, err = db.Exec("UPDATE tb_customer SET password = ? WHERE username = ?", encryptedNewPassword, username)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update password"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"})
		// c.JSON(http.StatusOK, gin.H{"message": "MASHOOOK"})
}

func changePassworForgotdHandler(c *gin.Context) {
    username := c.GetString("username") // Get the logged-in user's username

    var passwordData struct {
        NewPassword string `json:"password_baru"`
    }

    if err := c.ShouldBindJSON(&passwordData); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
        return
    }

    // Encrypt the new password before storing it
    encryptedNewPassword, err := encryptAES256CBC(passwordData.NewPassword)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to encrypt new password"})
        return
    }

    // Update the password in the database
    _, err = db.Exec("UPDATE tb_customer SET password = ? WHERE username = ?", encryptedNewPassword, username)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update password"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"})
		// c.JSON(http.StatusOK, gin.H{"message": "MASHOOOK"})
}

func updateProfileHandler(c *gin.Context) {
    username := c.GetString("username")
    var profileData Customer

    // Bind JSON data
    if err := c.ShouldBindJSON(&profileData); err != nil {
        log.Printf("Failed to bind JSON: %v", err)
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
        return
    }

    // Log the received data
    log.Printf("Received profile data: %+v", profileData)

    // Execute the SQL query
    result, err := db.Exec("UPDATE tb_customer SET nama = ?, alamat = ?, kabupaten = ?, kecamatan = ?, desa = ?, email = ? WHERE username = ?",
        profileData.Name, profileData.Address, profileData.Regency, profileData.District, profileData.Village, profileData.Email, username)
    if err != nil {
        log.Printf("Database error: %v", err)
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update profile", "msg": err.Error()})
        return
    }

    // Log the number of rows affected
    rowsAffected, _ := result.RowsAffected()
    log.Printf("Rows affected: %d", rowsAffected)

    c.JSON(http.StatusOK, gin.H{"message": "Profile updated successfully"})
}

func getProfileHandler(c *gin.Context) {
	username := c.GetString("username")
	var customer Customer

	err := db.QueryRow("SELECT username, email, nama, alamat, kecamatan, no_telp FROM tb_customer WHERE username = ?", username).Scan(
		&customer.Username, &customer.Email, &customer.Name, &customer.Address, &customer.District, &customer.Phone)
	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"error": "Profile not found"})
		return
	}

	c.JSON(http.StatusOK, customer)
}
// Handlers
type ImagePaths struct {
    ImageKitURL string
    LocalPath   string
    LocalURL    string
}

func downloadAndSaveImage(imagePath ImagePaths) error {
    // Create the directory if it doesn't exist
    dir := filepath.Dir(imagePath.LocalPath)
    if err := os.MkdirAll(dir, 0755); err != nil {
        return fmt.Errorf("failed to create directory: %v", err)
    }
		if _, err := os.Stat(imagePath.LocalPath); errors.Is(err, os.ErrNotExist) {
		  // path/to/whatever does not exist

    // Download the image
    resp, err := http.Get(imagePath.ImageKitURL + "?tr=w-133,h-153")
    if err != nil {
        return fmt.Errorf("failed to download image: %v", err)
    }
    defer resp.Body.Close()

    // Create the local file
    file, err := os.Create(imagePath.LocalPath)
    if err != nil {
        return fmt.Errorf("failed to create file: %v", err)
    }
    defer file.Close()

    // Copy the content
    _, err = io.Copy(file, resp.Body)
    if err != nil {
        return fmt.Errorf("failed to save image: %v", err)
    }
}
    return nil
}
func getFileNameFromURL(urlString string) string {
    // Parse the URL
    u, err := url.Parse(urlString)
    if err != nil {
        return ""
    }

    // Get the path component
    urlPath := u.Path

    // Extract the filename from the path
    fileName := path.Base(urlPath)

    // Remove any query parameters if present
    fileName = strings.Split(fileName, "?")[0]

    return fileName
}

// Get all products
func getProducts(c *gin.Context) {

		// Get kode_cabang from the request. This assumes you're passing it as a query parameter.
		// You could also get it from the request body or a custom header.
		kodeCabang := c.Query("kode_cabang")

		// Construct the query with the new join and WHERE clause.
		rows, err := db.Query(`
			SELECT
				b.kode_barang AS kodeBrg,
				b.nama_barang AS namaBrg,
				b.harga,
				b.berat,
				b.limit,
				b.harga_promo AS hargaPromo,
				b.image_url AS imageUrl,
				b.kode_golongan AS kodeGolongan,
				b.nama_golongan AS kategori,
				b.kode_promo AS kodePromo,
				b.deskripsi,
				COALESCE(s.stock, 0) AS stock,
				b.prioritas,
				b.bonusItem AS bonusItem,
				v.jenis_potongan AS jenisPotongan,
				v.jumlah_potongan AS jumlahPotongan
			FROM tm_barang b
			LEFT JOIN tb_voucher v ON v.id_target LIKE CONCAT('%', b.kode_barang, '%') AND v.aktif = 1 AND NOW() <= v.end_date AND v.is_redeem = 0
			LEFT JOIN tb_stockbrg s ON b.kode_barang = s.kode_barang AND s.kode_cabang = ?
			WHERE b.aktif = 'Y' AND b.kode_barang IS NOT NULL AND b.kode_barang != ""
			GROUP BY
				b.kode_barang
			ORDER BY
				(b.prioritas IS NULL) DESC,
				b.prioritas ASC
			;
		`, kodeCabang)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	var products []Product
	for rows.Next() {
		var product Product
		var hargaPromo sql.NullString
		var bonusItem sql.NullString

		if err := rows.Scan(
			&product.KodeBrg,
			&product.NamaBrg,
			&product.Harga,
			&product.Berat,
			&product.Limit,
			&hargaPromo,
			&product.ImageKitURL,
			&product.KodeGolongan,
			&product.Kategori,
			&product.KodePromo,
			&product.Deskripsi,
			&product.Stock,
			&product.Prioritas,
			&bonusItem,
			&product.JenisPotongan,
			&product.JumlahPotongan,
		); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		if product.ImageKitURL.Valid == false {

		}


		imagePath := ImagePaths{
        ImageKitURL: product.ImageKitURL.String,
        LocalPath:   "./images/" + getFileNameFromURL(product.ImageKitURL.String),
        LocalURL:    "https://api.bayiku.linksoftindo.com/images/" + getFileNameFromURL(product.ImageKitURL.String),
    }

		downloadAndSaveImage(imagePath)


		product.ImageURL = &imagePath.LocalURL
		// Assign pointer or nil to HargaPromo
		if hargaPromo.Valid {
			product.HargaPromo = &hargaPromo.String
		} else {
			product.HargaPromo = nil
		}

		if bonusItem.Valid {
			product.BonusItem = &bonusItem.String
		} else {
			product.BonusItem = nil
		}

		products = append(products, product)

	}


	c.JSON(http.StatusOK, products)
}

// Get all products
func getGrosirProducts(c *gin.Context) {
	// Get kode_cabang from the request. This assumes you're passing it as a query parameter.
	// You could also get it from the request body or a custom header.
	kodeCabang := c.Query("kode_cabang")

	// Construct the query with the new join and WHERE clause.
	rows, err := db.Query(`
		SELECT
			b.kode_barang AS kodeBrg,
			b.nama_barang AS namaBrg,
			b.harga_grosir AS harga,
			b.berat,
			b.limit,
			b.harga_promo AS hargaPromo,
			b.image_url AS imageUrl,
			b.kode_golongan AS kodeGolongan,
			b.nama_golongan AS kategori,
			b.kode_promo AS kodePromo,
			b.deskripsi,
			COALESCE(s.stock, 0) AS stock,
			b.prioritas,
			b.bonusItem AS bonusItem,
			v.jenis_potongan AS jenisPotongan,
			v.jumlah_potongan AS jumlahPotongan
		FROM tm_barang b
		LEFT JOIN tb_voucher v ON v.id_target LIKE CONCAT('%', b.kode_barang, '%') AND v.aktif = 1 AND NOW() <= v.end_date AND v.is_redeem = 0
		LEFT JOIN tb_stockbrg s ON b.kode_barang = s.kode_barang AND s.kode_cabang = ?
		WHERE b.aktif = 'Y' AND b.kode_barang IS NOT NULL AND b.kode_barang != ""
		GROUP BY
			b.kode_barang
		ORDER BY
			(b.prioritas IS NULL) DESC,
			b.prioritas ASC
		;
	`, kodeCabang)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	var products []Product
	for rows.Next() {
		var product Product
		var hargaPromo sql.NullString
		var bonusItem sql.NullString

		if err := rows.Scan(
			&product.KodeBrg,
			&product.NamaBrg,
			&product.Harga,
			&product.Berat,
			&product.Limit,
			&hargaPromo,
			&product.ImageKitURL,
			&product.KodeGolongan,
			&product.Kategori,
			&product.KodePromo,
			&product.Deskripsi,
			&product.Stock,
			&product.Prioritas,
			&bonusItem,
		); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		if product.ImageKitURL.Valid == false {

		}


		imagePath := ImagePaths{
        ImageKitURL: product.ImageKitURL.String,
        LocalPath:   "./images/" + getFileNameFromURL(product.ImageKitURL.String),
        LocalURL:    "https://api.bayiku.linksoftindo.com/images/" + getFileNameFromURL(product.ImageKitURL.String),
    }

		downloadAndSaveImage(imagePath)


		product.ImageURL = &imagePath.LocalURL
		// Assign pointer or nil to HargaPromo
		if hargaPromo.Valid {
			product.HargaPromo = &hargaPromo.String
		} else {
			product.HargaPromo = nil
		}

		if bonusItem.Valid {
			product.BonusItem = &bonusItem.String
		} else {
			product.BonusItem = nil
		}

		products = append(products, product)

	}


	c.JSON(http.StatusOK, products)
}

func getHomeProducts(c *gin.Context) {
	kodeCabang := c.Query("kode_cabang")
	rows, err := db.Query(`
		WITH RankedProducts AS (
		    SELECT
		        b.kode_barang AS kodeBrg,
		        b.nama_barang AS namaBrg,
		        b.harga,
		        b.berat,
		        b.limit,
		        b.harga_promo AS hargaPromo,  -- Corrected alias here
		        b.image_url AS imageUrl,
		        b.kode_golongan AS kodeGolongan,
		        b.nama_golongan AS kategori,
		        b.kode_promo AS kodePromo,
		        b.deskripsi,
		        COALESCE(s.stock, 0) AS stock,
		        b.prioritas,
		        b.bonusItem AS bonusItem,
		        v.jenis_potongan AS jenisPotongan,
		        v.jumlah_potongan AS jumlahPotongan,
		        ROW_NUMBER() OVER (PARTITION BY b.kode_golongan ORDER BY b.prioritas ASC) AS row_num
		    FROM tm_barang b
		    LEFT JOIN tb_voucher v ON v.id_target LIKE CONCAT('%', b.kode_barang, '%') AND v.aktif = 1 AND NOW() <= v.end_date AND v.is_redeem = 0
				LEFT JOIN tb_stockbrg s ON b.kode_barang = s.kode_barang AND s.kode_cabang = ?
		    WHERE b.aktif = 'Y'
		)
		SELECT
		    kodeBrg,
		    namaBrg,
		    harga,
		    berat,
		    "limit",
		    hargaPromo,  -- Using alias here
		    imageUrl,
		    kodeGolongan,
		    kategori,
		    kodePromo,
		    deskripsi,
		    stock,
		    prioritas,
		    bonusItem,
		    jenisPotongan,
		    jumlahPotongan
		FROM RankedProducts
		WHERE row_num <= 10
		ORDER BY kodeGolongan, row_num;
	`, kodeCabang)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	var products []Product
	for rows.Next() {
		var product Product
		var hargaPromo sql.NullString
		var bonusItem sql.NullString

		if err := rows.Scan(
			&product.KodeBrg,
			&product.NamaBrg,
			&product.Harga,
			&product.Berat,
			&product.Limit,
			&hargaPromo,
			&product.ImageKitURL,
			&product.KodeGolongan,
			&product.Kategori,
			&product.KodePromo,
			&product.Deskripsi,
			&product.Stock,
			&product.Prioritas,
			&bonusItem,
			&product.JenisPotongan,
			&product.JumlahPotongan,
		); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		if product.ImageKitURL.Valid == false {

		}


		imagePath := ImagePaths{
        ImageKitURL: product.ImageKitURL.String,
        LocalPath:   "./images/" + getFileNameFromURL(product.ImageKitURL.String),
        LocalURL:    "https://api.bayiku.linksoftindo.com/images/" + getFileNameFromURL(product.ImageKitURL.String),
    }

		downloadAndSaveImage(imagePath)


		product.ImageURL = &imagePath.LocalURL
		// Assign pointer or nil to HargaPromo
		if hargaPromo.Valid {
			product.HargaPromo = &hargaPromo.String
		} else {
			product.HargaPromo = nil
		}

		if bonusItem.Valid {
			product.BonusItem = &bonusItem.String
		} else {
			product.BonusItem = nil
		}

		products = append(products, product)

	}


	c.JSON(http.StatusOK, products)
}


// Get all promo products
func getPromoProducts(c *gin.Context) {
	kodeCabang := c.Query("kode_cabang")
	rows, err := db.Query(`
		SELECT
			b.kode_barang AS kodeBrg,
			b.nama_barang AS namaBrg,
			b.harga,
			b.berat,
			b.limit,
			b.harga_promo AS hargaPromo,
			b.image_url AS imageUrl,
			b.kode_golongan AS kodeGolongan,
			b.nama_golongan AS kategori,
			b.kode_promo AS kodePromo,
			b.deskripsi,
			COALESCE(s.stock, 0) AS stock,
			b.prioritas,
			COALESCE(bonusItem, '') AS bonusItem,
      v.jenis_potongan AS jenisPotongan,
      v.jumlah_potongan AS jumlahPotongan
		FROM tm_barang b LEFT JOIN tb_voucher v ON v.id_target LIKE CONCAT('%', b.kode_barang, '%') AND v.aktif = 1 AND NOW() <= v.end_date AND v.is_redeem = 0
		LEFT JOIN tb_stockbrg s ON b.kode_barang = s.kode_barang AND s.kode_cabang = ?
		WHERE b.promo = 'Y'
		AND b.aktif = 'Y'
		ORDER BY b.prioritas ASC
	`, kodeCabang)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	var products []Product
	for rows.Next() {
		var product Product
		var hargaPromo sql.NullString
		var bonusItem sql.NullString

		if err := rows.Scan(
			&product.KodeBrg,
			&product.NamaBrg,
			&product.Harga,
			&product.Berat,
			&product.Limit,
			&hargaPromo,
			&product.ImageKitURL,
			&product.KodeGolongan,
			&product.Kategori,
			&product.KodePromo,
			&product.Deskripsi,
			&product.Stock,
			&product.Prioritas,
			&bonusItem,
			&product.JenisPotongan,
			&product.JumlahPotongan,
		); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		if product.ImageKitURL.Valid == false {

		}


		imagePath := ImagePaths{
        ImageKitURL: product.ImageKitURL.String,
        LocalPath:   "./images/" + getFileNameFromURL(product.ImageKitURL.String),
        LocalURL:    "https://api.bayiku.linksoftindo.com/images/" + getFileNameFromURL(product.ImageKitURL.String),
    }

		downloadAndSaveImage(imagePath)


		product.ImageURL = &imagePath.LocalURL
		// Assign pointer or nil to HargaPromo
		if hargaPromo.Valid {
			product.HargaPromo = &hargaPromo.String
		} else {
			product.HargaPromo = nil
		}

		if bonusItem.Valid {
			product.BonusItem = &bonusItem.String
		} else {
			product.BonusItem = nil
		}

		products = append(products, product)

	}


	c.JSON(http.StatusOK, products)
}

// Get product by ID
func getProductByID(c *gin.Context) {
	id := c.Param("id")
	kodeCabang := c.Query("kode_cabang")
	var product Product
	var hargaPromo sql.NullString
	// var bonusItem sql.NullString
	err := db.QueryRow(`
		SELECT
			b.kode_barang AS kodeBrg,
			nama_barang AS namaBrg,
			harga,
			berat,
			` + "`limit`" + `,
			harga_promo AS hargaPromo,
			image_url AS imageUrl,
			kode_golongan AS kodeGolongan,
			nama_golongan AS kategori,
			kode_promo AS kodePromo,
			deskripsi,
			COALESCE(s.stock, 0) AS stock,
			prioritas,
			COALESCE(b.bonusItem, '') AS bonusItem,
      v.jenis_potongan AS jenisPotongan,
      v.jumlah_potongan AS jumlahPotongan
		FROM tm_barang b LEFT JOIN tb_voucher v ON v.id_target LIKE CONCAT('%', b.kode_barang, '%') AND v.aktif = 1 AND NOW() <= v.end_date AND v.is_redeem = 0
		LEFT JOIN tb_stockbrg s ON b.kode_barang = s.kode_barang AND s.kode_cabang = ?
		WHERE b.kode_barang = ?`, kodeCabang, id).Scan(
			&product.KodeBrg,
			&product.NamaBrg,
			&product.Harga,
			&product.Berat,
			&product.Limit,
			&hargaPromo,
			&product.ImageKitURL,
			&product.KodeGolongan,
			&product.Kategori,
			&product.KodePromo,
			&product.Deskripsi,
			&product.Stock,
			&product.Prioritas,
			&product.BonusItem,
			&product.JenisPotongan,
			&product.JumlahPotongan,
	)

	if product.ImageKitURL.Valid == false {

	}


	imagePath := ImagePaths{
			ImageKitURL: product.ImageKitURL.String,
			LocalPath:   "./images/" + getFileNameFromURL(product.ImageKitURL.String),
			LocalURL:    "https://api.bayiku.linksoftindo.com/images/" + getFileNameFromURL(product.ImageKitURL.String),
	}

	downloadAndSaveImage(imagePath)


	product.ImageURL = &imagePath.LocalURL

	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"message": "Product not found"})
		return
	} else if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, product)
}

// Get product by ID
func getProductGrosirByID(c *gin.Context) {
	id := c.Param("id")
	kodeCabang := c.Query("kode_cabang")
	var product Product
	var hargaPromo sql.NullString
	var bonusItem sql.NullString
	err := db.QueryRow(`
		SELECT
			b.kode_barang AS kodeBrg,
			nama_barang AS namaBrg,
			harga_grosir AS harga,
			berat,
			` + "`limit`" + `,
			harga_promo AS hargaPromo,
			image_url AS imageUrl,
			kode_golongan AS kodeGolongan,
			nama_golongan AS kategori,
			kode_promo AS kodePromo,
			deskripsi,
			COALESCE(s.stock, 0) AS stock,
			prioritas,
			COALESCE(bonusItem, '') AS bonusItem,
			min_qty_grosir AS minQtyGrosir
		FROM tm_barang b
		LEFT JOIN tb_stockbrg s ON b.kode_barang = s.kode_barang AND s.kode_cabang = ?
		WHERE b.kode_barang = ?`, kodeCabang, id).Scan(
			&product.KodeBrg,
			&product.NamaBrg,
			&product.Harga,
			&product.Berat,
			&product.Limit,
			&hargaPromo,
			&product.ImageKitURL,
			&product.KodeGolongan,
			&product.Kategori,
			&product.KodePromo,
			&product.Deskripsi,
			&product.Stock,
			&product.Prioritas,
			&bonusItem,
			&product.MinQtyGrosir,
	)

	if product.ImageKitURL.Valid == false {

	}


	imagePath := ImagePaths{
			ImageKitURL: product.ImageKitURL.String,
			LocalPath:   "./images/" + getFileNameFromURL(product.ImageKitURL.String),
			LocalURL:    "https://api.bayiku.linksoftindo.com/images/" + getFileNameFromURL(product.ImageKitURL.String),
	}

	downloadAndSaveImage(imagePath)


	product.ImageURL = &imagePath.LocalURL

	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"message": "Product not found"})
		return
	} else if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, product)
}

// Get product by ID Golongan
func getProductByIDGolongan(c *gin.Context) {
	id := c.Param("id")
	kodeCabang := c.Query("kode_cabang")

	// Use db.Query to fetch multiple rows
	rows, err := db.Query(`
		SELECT
			b.kode_barang AS kodeBrg,
			nama_barang AS namaBrg,
			harga,
			berat,
			` + "`limit`" + `,
			harga_promo AS hargaPromo,
			image_url AS imageUrl,
			kode_golongan AS kodeGolongan,
			nama_golongan AS kategori,
			kode_promo AS kodePromo,
			deskripsi,
			COALESCE(s.stock, 0) AS stock,
			prioritas,
			COALESCE(bonusItem, '') AS bonusItem,
			v.jenis_potongan AS jenisPotongan,
			v.jumlah_potongan AS jumlahPotongan
		FROM tm_barang b
		LEFT JOIN tb_voucher v ON v.id_target LIKE CONCAT('%', b.kode_barang, '%') AND v.aktif = 1 AND NOW() <= v.end_date AND v.is_redeem = 0
		LEFT JOIN tb_stockbrg s ON b.kode_barang = s.kode_barang AND s.kode_cabang = ?
		WHERE b.kode_golongan = ? AND b.aktif = 'Y'`, kodeCabang, id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	var products []Product
	for rows.Next() {
		var product Product
		var hargaPromo sql.NullString
		var bonusItem sql.NullString

		if err := rows.Scan(
			&product.KodeBrg,
			&product.NamaBrg,
			&product.Harga,
			&product.Berat,
			&product.Limit,
			&hargaPromo,
			&product.ImageKitURL,
			&product.KodeGolongan,
			&product.Kategori,
			&product.KodePromo,
			&product.Deskripsi,
			&product.Stock,
			&product.Prioritas,
			&bonusItem,
			&product.JenisPotongan,
			&product.JumlahPotongan,
		); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		if product.ImageKitURL.Valid == false {

		}


		imagePath := ImagePaths{
        ImageKitURL: product.ImageKitURL.String,
        LocalPath:   "./images/" + getFileNameFromURL(product.ImageKitURL.String),
        LocalURL:    "https://api.bayiku.linksoftindo.com/images/" + getFileNameFromURL(product.ImageKitURL.String),
    }

		downloadAndSaveImage(imagePath)


		product.ImageURL = &imagePath.LocalURL
		// Assign pointer or nil to HargaPromo
		if hargaPromo.Valid {
			product.HargaPromo = &hargaPromo.String
		} else {
			product.HargaPromo = nil
		}

		if bonusItem.Valid {
			product.BonusItem = &bonusItem.String
		} else {
			product.BonusItem = nil
		}

		products = append(products, product)

	}


	c.JSON(http.StatusOK, products)
}

// Get product by ID Golongan
func getProductGrosirByIDGolongan(c *gin.Context) {
	id := c.Param("id")
	kodeCabang := c.Query("kode_cabang")

	// Use db.Query to fetch multiple rows
	rows, err := db.Query(`
		SELECT
			b.kode_barang AS kodeBrg,
			nama_barang AS namaBrg,
			harga_grosir AS harga,
			berat,
			` + "`limit`" + `,
			harga_promo AS hargaPromo,
			image_url AS imageUrl,
			kode_golongan AS kodeGolongan,
			nama_golongan AS kategori,
			kode_promo AS kodePromo,
			deskripsi,
			COALESCE(s.stock, 0) AS stock,
			prioritas,
			COALESCE(bonusItem, '') AS bonusItem,
			min_qty_grosir AS minQtyGrosir
		FROM tm_barang b
		LEFT JOIN tb_stockbrg s ON b.kode_barang = s.kode_barang AND s.kode_cabang = ?
		WHERE b.kode_golongan = ? AND b.aktif = 'Y'`, kodeCabang, id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	var products []Product
	for rows.Next() {
		var product Product
		var hargaPromo sql.NullString
		var bonusItem sql.NullString

		if err := rows.Scan(
			&product.KodeBrg,
			&product.NamaBrg,
			&product.Harga,
			&product.Berat,
			&product.Limit,
			&hargaPromo,
			&product.ImageKitURL,
			&product.KodeGolongan,
			&product.Kategori,
			&product.KodePromo,
			&product.Deskripsi,
			&product.Stock,
			&product.Prioritas,
			&bonusItem,
			&product.MinQtyGrosir,
		); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		if product.ImageKitURL.Valid == false {

		}


		imagePath := ImagePaths{
        ImageKitURL: product.ImageKitURL.String,
        LocalPath:   "./images/" + getFileNameFromURL(product.ImageKitURL.String),
        LocalURL:    "https://api.bayiku.linksoftindo.com/images/" + getFileNameFromURL(product.ImageKitURL.String),
    }

		downloadAndSaveImage(imagePath)


		product.ImageURL = &imagePath.LocalURL
		// Assign pointer or nil to HargaPromo
		if hargaPromo.Valid {
			product.HargaPromo = &hargaPromo.String
		} else {
			product.HargaPromo = nil
		}

		if bonusItem.Valid {
			product.BonusItem = &bonusItem.String
		} else {
			product.BonusItem = nil
		}

		products = append(products, product)

	}


	c.JSON(http.StatusOK, products)
}

// Create a new product
func createProduct(c *gin.Context) {
	var newProduct Product
	if err := c.ShouldBindJSON(&newProduct); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	result, err := db.Exec(`
		INSERT INTO tm_barang
		(kode_barang, nama_barang, harga, berat, `+"`limit`"+`, harga_promo, image_url, kode_golongan, nama_golongan, kode_promo, deskripsi, stock_akhir, prioritas, bonusItem)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		newProduct.KodeBrg,
		newProduct.NamaBrg,
		newProduct.Harga,
		newProduct.Berat,
		newProduct.Limit,
		newProduct.HargaPromo,
		newProduct.ImageURL,
		newProduct.KodeGolongan,
		newProduct.Kategori,
		newProduct.KodePromo,
		newProduct.Deskripsi,
		newProduct.Stock,
		newProduct.Prioritas,
		newProduct.BonusItem,
	)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	id, _ := result.LastInsertId()
	newProduct.KodeBrg = strconv.FormatInt(id, 10)
	c.JSON(http.StatusCreated, newProduct)
}

// Update an existing product
func updateProduct(c *gin.Context) {
	id := c.Param("id")
	var updatedProduct Product
	if err := c.ShouldBindJSON(&updatedProduct); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	_, err := db.Exec(`
		UPDATE tm_barang
		SET nama_barang = ?, harga = ?, berat = ?, `+"`limit`"+` = ?, harga_promo = ?, image_url = ?, kode_golongan = ?, nama_golongan = ?, kode_promo = ?, deskripsi = ?, stock_akhir = ?, prioritas = ?, bonusItem = ?
		WHERE kode_barang = ?`,
		updatedProduct.NamaBrg,
		updatedProduct.Harga,
		updatedProduct.Berat,
		updatedProduct.Limit,
		updatedProduct.HargaPromo,
		updatedProduct.ImageURL,
		updatedProduct.KodeGolongan,
		updatedProduct.Kategori,
		updatedProduct.KodePromo,
		updatedProduct.Deskripsi,
		updatedProduct.Stock,
		updatedProduct.Prioritas,
		updatedProduct.BonusItem,
		id,
	)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, updatedProduct)
}

// Delete a product
func deleteProduct(c *gin.Context) {
	id := c.Param("id")
	_, err := db.Exec("DELETE FROM tm_barang WHERE kode_barang = ?", id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "Product deleted"})
}


// ORDERS START
func showOrdersHandler(c *gin.Context) {
	username := c.GetString("username")

	rows, err := db.Query(`
		SELECT kode_order, DATE_FORMAT(tanggal, '%d %M %Y %H:%i.%s') AS tanggal, username, nama_cabang, nama_cust, alamat_cust, ongkir, discount, nilai, cara_bayar, status_order, batal_oleh, ket_batal
		FROM tr_order_m m
		LEFT JOIN tb_cabang b ON m.kode_cabang = b.kode_cabang
		WHERE username = ? AND tanggal > DATE_SUB(CURDATE(), INTERVAL 1 MONTH)
		ORDER BY kode_order DESC`, username)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	var orders []Order
	for rows.Next() {
		var order Order
		err := rows.Scan(&order.KodeOrder, &order.Tanggal, &order.Username, &order.NamaCabang, &order.NamaCust, &order.AlamatCust, &order.Ongkir, &order.Discount, &order.Nilai, &order.CaraBayar, &order.StatusOrder, &order.BatalOleh, &order.KetBatal)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		itemRows, err := db.Query("SELECT kode_order, kode_barang, nama_barang, image_url, jumlah, berat, harga, harga_promo, harga_jual, promo, isFree FROM tr_order_d WHERE kode_order = ?", order.KodeOrder)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		defer itemRows.Close()

		var items []OrderItem
		for itemRows.Next() {
			var item OrderItem
			if err := itemRows.Scan(&item.KodeOrder, &item.KodeBarang, &item.NamaBarang, &item.ImageURL, &item.Jumlah, &item.Berat, &item.Harga, &item.HargaPromo, &item.HargaJual, &item.Promo, &item.IsFree); err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
			items = append(items, item)
		}

		order.Items = items
		orders = append(orders, order)
	}

	c.JSON(http.StatusOK, orders)
}

func insertOrderHandler(c *gin.Context) {
	var order Order
	if err := c.ShouldBindJSON(&order); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input", "details": err.Error()})
		return
	}

	// Generate new order code
	var newKodeOrder string
	err := db.QueryRow(`SELECT IFNULL(MAX(kode_order) + 1, CONCAT(DATE_FORMAT(NOW(), '%y%m'), '000001')) AS kode_order
		FROM tr_order_m
		WHERE LENGTH(kode_order) = 10 AND LEFT(kode_order, 4) = DATE_FORMAT(NOW(), '%y%m')`).Scan(&newKodeOrder)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate order code"})
		return
	}

	// Start a transaction
	tx, err := db.Begin()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to start transaction"})
		return
	}

	// Insert order into tr_order_m
	_, err = tx.Exec(`
		INSERT INTO tr_order_m (kode_order, tanggal, username, nama_cust, alamat_cust, lat, `+"`long`"+`, ongkir, discount, nilai, cara_bayar, status_order, versi, kode_cabang, kode_voucher)
		VALUES (?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		newKodeOrder, order.Username, order.NamaCust, order.AlamatCust, order.Lat, order.Long, order.Ongkir, order.Discount, order.Nilai, order.CaraBayar, "Proses", order.Versi, order.KodeCabang, order.KodeVoucher)
	if err != nil {
		tx.Rollback()
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to insert order","msg": err})
		return
	}

	// Insert items into tr_order_d
	for i, item := range order.Items {
		_, err := tx.Exec(`
			INSERT INTO tr_order_d (item, kode_order, kode_barang, nama_barang, image_url, jumlah, berat, harga, harga_promo, harga_jual, promo, isFree)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			i+1, newKodeOrder, item.KodeBarang, item.NamaBarang, item.ImageURL, item.Jumlah, item.Berat, item.Harga, item.HargaPromo, item.HargaJual, item.Promo, item.IsFree)
		if err != nil {
			tx.Rollback()
			c.JSON(http.StatusInternalServerError, gin.H{
				"error":        "Failed to insert order items",
				"sql_query":    "INSERT INTO tr_order_d (item, kode_order, kode_barang, nama_barang, image_url, jumlah, berat, harga, harga_promo, harga_jual, promo, isFree) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
				"sql_values":   []interface{}{i+1, newKodeOrder, item.KodeBarang, item.NamaBarang, item.ImageURL, item.Jumlah, item.Berat, item.Harga, item.HargaPromo, item.HargaJual, item.Promo, item.IsFree},
				"error_detail": err.Error(),
			})
			return
		}
	}

	// Commit the transaction
	if err := tx.Commit(); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Transaction failed"})
		return
	}

	c.JSON(http.StatusCreated, gin.H{"message": "Order successfully created", "kodeOrder": newKodeOrder})
}

func batalOrderHandler(c *gin.Context) {
	var order struct {
		KodeOrder string `json:"kodeOrder"`
		KetBatal  string `json:"ketBatal"`
	}

	if err := c.ShouldBindJSON(&order); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
		return
	}

	_, err := db.Exec(`
		UPDATE tr_order_m
		SET status_order = 'Batal', batal_oleh = 'User', ket_batal = ?
		WHERE kode_order = ?`, order.KetBatal, order.KodeOrder)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to cancel order"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Order successfully canceled"})
}
// ORDERS END


// SETTINGS START
func getBannerHandler(c *gin.Context) {
	username := c.GetString("username")

	var banner Banner
	err := db.QueryRow("SELECT image_url FROM tb_banner WHERE aktif = 'Y' LIMIT 1").Scan(&banner.ImageURL)
	if err == sql.ErrNoRows {
		// No active banner found, use default
		banner.ImageURL = "https://ik.imagekit.io/linksoft/banner/default-banner_YzInpHc4F.jpg"
	} else if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch banner"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"username": username, "url": banner.ImageURL})
}

func getInfoHandler(c *gin.Context) {
	username := c.GetString("username")

	var info Info
	err := db.QueryRow(`
		SELECT id, nama_info, kode_info, keterangan, mode, payload
		FROM tb_info
		WHERE aktif = 1
		LIMIT 1
	`).Scan(&info.ID, &info.NamaInfo, &info.KodeInfo, &info.Keterangan, &info.Mode.Type, &info.Mode.Payload)
	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"message": "Setup info tidak ditemukan!"})
		return
	} else if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch info"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"username": username, "info": info})
}
// SETTINGS END

// VOUCHER START


func getVoucher(c *gin.Context) {

	var voucher Voucher
	err := db.QueryRow(`
		SELECT kode_voucher, start_date, end_date, target_voucher, id_target, jenis_potongan, jumlah_potongan
		FROM tb_voucher
		WHERE aktif = 1 AND NOW() <= end_date
	`).Scan(&voucher.KodeVoucher, &voucher.StartDate, &voucher.EndDate, &voucher.TargetVoucher, &voucher.IdTarget, &voucher.JenisPotongan, &voucher.JumlahPotongan)
	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"message": "Voucher tidak ditemukan!"})
		return
	} else if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch voucher"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"voucher": voucher})
}

func getVoucherCode(c *gin.Context) {
	code := c.Param("code")
	username := c.Query("username") // Get username from query parameters

	fmt.Println("Received voucher code:", code)
	fmt.Println("Received username:", username) // Log the received username

	var voucher Voucher
	err := db.QueryRow(`
		SELECT kode_voucher, start_date, end_date, target_voucher, id_target, jenis_potongan, jumlah_potongan, is_double_disc
		FROM tb_voucher
		WHERE aktif = 1 AND is_redeem = 1 AND kode_voucher = ? AND NOW() <= end_date AND limit_voucher > 0
		LIMIT 1
	`, code).Scan(&voucher.KodeVoucher, &voucher.StartDate, &voucher.EndDate, &voucher.TargetVoucher, &voucher.IdTarget, &voucher.JenisPotongan, &voucher.JumlahPotongan, &voucher.IsDoubleDisc)
	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"message": "Voucher tidak ditemukan!", "code": code})
		return
	} else if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch info"})
		return
	}

	// Check if the voucher has already been used by the user
	// var usedCount int
	// err = db.QueryRow(`
	// 	SELECT COUNT(*)
	// 	FROM tr_order_m
	// 	WHERE kode_voucher = ? AND username = ? AND status_order NOT 'Batal'
	// `, code, username).Scan(&usedCount)
	// if err != nil {
	// 	c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check voucher usage"})
	// 	return
	// }
	//
	// if usedCount > 0 {
	// 	c.JSON(http.StatusForbidden, gin.H{"message": "Voucher sudah digunakan oleh pengguna ini."})
	// 	return
	// }

	// Check tb_setupall
	var limitActive int
	err = db.QueryRow(`
		SELECT nilaiint
		FROM tb_setupall
		WHERE tb_setupall.group = 'voucher' AND subgroup = 'limit'
	`).Scan(&limitActive)
	if err != nil && err != sql.ErrNoRows {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check voucher limit settings"})
		return
	}

	// Only check voucher usage if limit is active
	if limitActive == 1 {
		var usedCount int
		err = db.QueryRow(`
			SELECT COUNT(*)
			FROM tr_order_m
			WHERE kode_voucher = ? AND username = ? AND status_order NOT 'Batal'
		`, code, username).Scan(&usedCount)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check voucher usage"})
			return
		}

		if usedCount > 0 {
			c.JSON(http.StatusForbidden, gin.H{"message": "Voucher sudah digunakan oleh pengguna ini."})
			return
		}
	}

	c.JSON(http.StatusOK, gin.H{"voucher": voucher})
}

func createVoucher(c *gin.Context) {
	var newVoucher Voucher
	if err := c.ShouldBindJSON(&newVoucher); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request", "message": newVoucher})
		return
	}

	_, err := db.Exec("INSERT INTO tb_voucher (kode_voucher, start_date, end_date, target_voucher, id_target, jenis_potongan, jumlah_potongan) VALUES (?, ?, ?, ?, ?, ?, ?)",
		newVoucher.KodeVoucher, newVoucher.StartDate, newVoucher.EndDate, newVoucher.TargetVoucher, newVoucher.IdTarget, newVoucher.JenisPotongan, newVoucher.JumlahPotongan)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err})
		return
	}

	c.JSON(http.StatusCreated, gin.H{"message": "Voucher successfully created"})
}
// VOUCHER END

// CABANG START

func getCabang(c *gin.Context) {

		//
    // // Log the incoming request details
    // log.Printf("Request Headers: %v\n", c.Request.Header)
    // log.Printf("Request Method: %s\n", c.Request.Method)
    // log.Printf("Request URL: %s\n", c.Request.URL)

    rows, err := db.Query(`
        SELECT b.kode_cabang, b.nama_cabang, b.lat, b.long, b.keterangan, k.persen, b.aktif
        FROM tb_cabang b
				LEFT JOIN tb_kelompok_harga k ON b.kode_kelompok = k.kode_kelompok
        WHERE b.aktif = 'Y'
    `)
    if err != nil {
        log.Printf("Database error: %v\n", err)
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Cabang", "details": err.Error()})
        return
    }
    defer rows.Close()

    var cabangs []Cabang
    for rows.Next() {
        var cabang Cabang
        if err := rows.Scan(&cabang.KodeCabang, &cabang.NamaCabang, &cabang.Lat, &cabang.Long, &cabang.Keterangan, &cabang.Persen, &cabang.Aktif); err != nil {
            // log.Printf("Error scanning row: %v\n", err)
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Cabang", "details": err.Error()})
            return
        }
        cabangs = append(cabangs, cabang)
    }

    if err := rows.Err(); err != nil {
        // log.Printf("Error iterating rows: %v\n", err)
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Cabang", "details": err.Error()})
        return
    }

    if len(cabangs) == 0 {

        c.JSON(http.StatusNotFound, gin.H{"message": "Cabang tidak ditemukan!"})
        return
    }

    // log.Printf("Cabang fetched successfully: %+v\n", cabangs)
    c.JSON(http.StatusOK, gin.H{"cabangs": cabangs})
}

// CABANG END

// SETUP START
func getOngkirHandler(c *gin.Context) {
	rows, err := db.Query("SELECT `group`, subgroup, nilaiint as nilai FROM tb_setupall")
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch ongkir data", "detail": err})
		return
	}
	defer rows.Close()

	dataSetup := make(map[string]int)
	for rows.Next() {
		var item Setup
		if err := rows.Scan(&item.Group, &item.Subgroup, &item.Nilai); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan ongkir data", "detail": err})
			return
		}
		dataSetup[item.Subgroup] = item.Nilai
	}

	// Verify the request (assuming AUTHORIZATION::verify_request is a JWT verification)
	username := c.GetString("username")
	if username == "" {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	// If verification is successful, return the data
	c.JSON(http.StatusOK, dataSetup)
}
// SETUP END


// OTP THINGS
// sendOTPHandler sends an OTP message to a user via WhatsApp and stores it in the database
func sendOTPHandler(c *gin.Context) {
	// Parse the request body
	var request struct {
		PhoneNumber     string `json:"phone_number"`      // Recipient's phone number
		RealPhoneNumber string `json:"real_phone_number"` // Recipient's real phone number (username)
	}
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
		return
	}

	// Check if the real_phone_number is already registered as a username
	var username string
	err := db.QueryRow("SELECT username FROM tb_customer WHERE username = ?", request.RealPhoneNumber).Scan(&username)
	if err == nil {
		// If the username exists, return an error
		c.JSON(http.StatusBadRequest, gin.H{"error": "Phone number is already registered as a username"})
		return
	} else if err != sql.ErrNoRows {
		// If there's a database error, log it and return an internal server error
		log.Printf("Failed to execute SQL: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check user registration"})
		return
	}

	// Generate a random 6-digit OTP
	rand.Seed(time.Now().UnixNano()) // Seed the random number generator
	otp := fmt.Sprintf("%06d", rand.Intn(1000000)) // Generate a 6-digit OTP

	// Set the OTP expiration time (5 minutes from now)
	expiresAt := time.Now().Add(5 * time.Minute)

	// Send the OTP via WhatsApp first
	err = sendWhatsAppMessage(request.PhoneNumber, fmt.Sprintf("Your OTP is: %s", otp))
	if err != nil {
		// If WhatsApp fails to send, return an error and do not store the OTP
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send OTP via WhatsApp", "details": err})
		return
	}

	// Log the SQL query with parameters
	log.Printf("Executing SQL: INSERT INTO tb_otp (phone_number, otp, expires_at) VALUES ('%s', '%s', '%s')",
		request.PhoneNumber, otp, expiresAt.Format("2006-01-02 15:04:05"))

	// Execute the SQL query to store the OTP
	_, err = db.Exec("INSERT INTO tb_otp (phone_number, otp, expires_at) VALUES (?, ?, ?)",
		request.PhoneNumber, otp, expiresAt)
	if err != nil {
		log.Printf("Failed to execute SQL: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to store OTP in database"})
		return
	}

	// Return success response
	c.JSON(http.StatusOK, gin.H{"message": "OTP sent successfully!"})
}

// sendOTPHandler sends an OTP message to a user via WhatsApp and stores it in the database
func sendOTPForgotHandler(c *gin.Context) {
	// Parse the request body
	var request struct {
		PhoneNumber     string `json:"phone_number"`      // Recipient's phone number
		RealPhoneNumber string `json:"real_phone_number"` // Recipient's real phone number (username)
	}
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
		return
	}

	// Generate a random 6-digit OTP
	rand.Seed(time.Now().UnixNano()) // Seed the random number generator
	otp := fmt.Sprintf("%06d", rand.Intn(1000000)) // Generate a 6-digit OTP

	// Set the OTP expiration time (5 minutes from now)
	expiresAt := time.Now().Add(5 * time.Minute)

	// Send the OTP via WhatsApp first
	err := sendWhatsAppMessage(request.PhoneNumber, fmt.Sprintf("Your OTP is: %s", otp))
	if err != nil {
		// If WhatsApp fails to send, return an error and do not store the OTP
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send OTP via WhatsApp", "details": err})
		return
	}

	// Log the SQL query with parameters
	log.Printf("Executing SQL: INSERT INTO tb_otp (phone_number, otp, expires_at) VALUES ('%s', '%s', '%s')",
		request.PhoneNumber, otp, expiresAt.Format("2006-01-02 15:04:05"))

	// Execute the SQL query to store the OTP
	_, err = db.Exec("INSERT INTO tb_otp (phone_number, otp, expires_at) VALUES (?, ?, ?)",
		request.PhoneNumber, otp, expiresAt)
	if err != nil {
		log.Printf("Failed to execute SQL: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to store OTP in database"})
		return
	}

	// Return success response
	c.JSON(http.StatusOK, gin.H{"message": "OTP sent successfully!"})
}

// verifyOTPHandler verifies the OTP provided by the user
func verifyOTPHandler(c *gin.Context) {
	// Parse the request body
	var request struct {
		PhoneNumber string `json:"phone_number"` // Recipient's phone number
		OTP         string `json:"otp"`         // OTP to verify
	}
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
		return
	}

	// Fetch the OTP from the database
	var storedOTP string
	var expiresAt time.Time // Use time.Time to store the expiration time
	err := db.QueryRow(`
		SELECT otp, expires_at
		FROM tb_otp
		WHERE phone_number = ?
		ORDER BY created_at DESC
		LIMIT 1`,
		request.PhoneNumber).Scan(&storedOTP, &expiresAt)

	if err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "No OTP found for this phone number"})
		} else {
			// Log the exact error for debugging
			log.Printf("Database error: %v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch OTP from database", "details": err.Error()})
		}
		return
	}

	// Log the expiration time for debugging
	log.Printf("Expiration Time: %s", expiresAt.Format("2006-01-02 15:04:05"))

	// Check if the OTP has expired
	currentTime := time.Now()
	if currentTime.After(expiresAt) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "OTP has expired"})
		return
	}

	// Verify the OTP
	if request.OTP != storedOTP {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid OTP"})
		return
	}

	// Return success response
	c.JSON(http.StatusOK, gin.H{"message": "OTP verified successfully!"})
}

// verifyOTPHandler verifies the OTP provided by the user
func verifyForgotOTPHandler(c *gin.Context) {
	// Parse the request body
	var request struct {
		PhoneNumber string `json:"phone_number"` // Recipient's phone number
		PhoneNumberAsli string `json:"phone_number_asli"` // Recipient's phone number
		OTP         string `json:"otp"`         // OTP to verify
	}
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
		return
	}

	// Fetch the OTP from the database
	var storedOTP string
	var expiresAt time.Time // Use time.Time to store the expiration time
	err := db.QueryRow(`
		SELECT otp, expires_at
		FROM tb_otp
		WHERE phone_number = ?
		ORDER BY created_at DESC
		LIMIT 1`,
		request.PhoneNumber).Scan(&storedOTP, &expiresAt)

	if err != nil {
		if err == sql.ErrNoRows {
			log.Printf("errornya: ", err)
			c.JSON(http.StatusNotFound, gin.H{"error": "No OTP found for this phone number"})
		} else {
			// Log the exact error for debugging
			log.Printf("Database error: %v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch OTP from database", "details": err.Error()})
		}
		return
	}

	// Log the expiration time for debugging
	log.Printf("Expiration Time: %s", expiresAt.Format("2006-01-02 15:04:05"))

	// Check if the OTP has expired
	currentTime := time.Now()
	if currentTime.After(expiresAt) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "OTP has expired"})
		return
	}

	// Verify the OTP
	if request.OTP != storedOTP {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid OTP"})
		return
	}

	var customer Customer
	err = db.QueryRow("SELECT username,password, nama, alamat,kecamatan,no_telp,email FROM tb_customer WHERE username = ?", request.PhoneNumberAsli).Scan(&customer.Username, &customer.Password, &customer.Name, &customer.Address, &customer.District, &customer.Phone, &customer.Email)
	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"error": "Invalid username or password"})
		return
	}

	token, err := generateToken(customer.Username)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"token": token, "data": customer, "status": http.StatusOK})
}



// sendWhatsAppMessage sends a message via WhatsApp
// Updated sendWhatsAppMessage function using the working code pattern
func sendWhatsAppMessage(phoneNumber, message string) error {
	client, err := getWhatsAppClient()
	if err != nil {
		return fmt.Errorf("failed to get WhatsApp client: %v", err)
	}

	if !client.IsLoggedIn() {
		return errors.New("WhatsApp client is not logged in")
	}

	// Normalize phone number
	normalizedTo, err := normalizePhoneNumber(phoneNumber)
	if err != nil {
		return fmt.Errorf("failed to normalize phone number: %v", err)
	}

	// Force reconnect to ensure fresh session
	if client.IsConnected() {
		log.Println("Disconnecting to ensure fresh session for send")
		client.Disconnect()
		time.Sleep(1 * time.Second)
	}
	if err := client.Connect(); err != nil {
		return fmt.Errorf("reconnect failed: %v", err)
	}

	// Parse JID with normalized number
	jid := types.NewJID(normalizedTo, types.DefaultUserServer)
	msg := &waE2E.Message{
		Conversation: &message,
	}

	// Use dedicated timeout context (120s) and retry logic
	const maxRetries = 3
	const retryDelay = 5 * time.Second
	var sendErr error
	for attempt := 1; attempt <= maxRetries; attempt++ {
		ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
		_, err := client.SendMessage(ctx, jid, msg)
		cancel()

		if err == nil {
			log.Printf("Message sent successfully to %s", normalizedTo)
			return nil
		}

		sendErr = err
		log.Printf("Send attempt %d failed: %v", attempt, err)
		if attempt < maxRetries {
			time.Sleep(retryDelay)
		}
	}

	return fmt.Errorf("send failed after retries: %v", sendErr)
}

// WhatsApp handlers from working code
func whatsappLoginHandler(c *gin.Context) {
	client, err := getWhatsAppClient()
	if err != nil {
		c.JSON(http.StatusInternalServerError, LoginResponse{Status: "client_error", Error: err.Error()})
		return
	}

	if client.IsLoggedIn() {
		c.JSON(http.StatusOK, LoginResponse{Status: "already_logged_in"})
		return
	}

	// Force disconnect if any stale connection exists
	if client.IsConnected() {
		log.Println("Disconnecting existing connection to initiate login")
		client.Disconnect()
		time.Sleep(1 * time.Second)
	}

	qrChan, _ := client.GetQRChannel(context.Background())
	if err := client.Connect(); err != nil {
		c.JSON(http.StatusInternalServerError, LoginResponse{Status: "connect_failed", Error: err.Error()})
		return
	}

	select {
	case evt := <-qrChan:
		if evt.Event == "code" {
			// Generate ASCII QR code
			asciiQR, err := generateASCIIQR(evt.Code)
			if err != nil {
				log.Println("Failed to generate ASCII QR:", err)
				asciiQR = evt.Code // Fallback to raw text
			}

			// Print to terminal
			fmt.Println("Scan this QR code with WhatsApp (Linked Devices):")
			fmt.Println(asciiQR)

			// Return QR code immediately in response
			c.JSON(http.StatusOK, LoginResponse{Status: "qr_generated", QR: asciiQR})

			// Continue waiting for login in background
			go func() {
				ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
				defer cancel()
				for {
					select {
					case evt := <-qrChan:
						if evt.Event == "error" {
							log.Println("Login failed:", evt.Error.Error())
							return
						} else if evt.Event == "success" {
							log.Println("Login successful")
							return
						}
					case <-ctx.Done():
						log.Println("Login timed out")
						return
					}
				}
			}()
			return
		}
	case <-time.After(10 * time.Second):
		c.JSON(http.StatusGatewayTimeout, LoginResponse{Status: "no_qr_generated"})
	}
}

func whatsappSendHandler(c *gin.Context) {
	var req SendRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, SendResponse{Status: "error", Error: err.Error()})
		return
	}

	client, err := getWhatsAppClient()
	if err != nil {
		c.JSON(http.StatusInternalServerError, SendResponse{Status: "error", Error: err.Error()})
		return
	}

	if !client.IsLoggedIn() {
		c.JSON(http.StatusUnauthorized, SendResponse{Status: "error", Error: "not_logged_in"})
		return
	}

	// Use the updated sendWhatsAppMessage function
	err = sendWhatsAppMessage(req.To, req.Message)
	if err != nil {
		c.JSON(http.StatusInternalServerError, SendResponse{Status: "error", Error: err.Error()})
		return
	}

	c.JSON(http.StatusOK, SendResponse{Status: "sent", Message: "Message sent successfully"})
}

func whatsappStatusHandler(c *gin.Context) {
	client, err := getWhatsAppClient()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	connected := client.IsConnected()
	loggedIn := client.IsLoggedIn()

	if loggedIn && !connected {
		// Reconnect if logged in but disconnected
		log.Println("Reconnecting for status check")
		_ = client.Connect()
		time.Sleep(2 * time.Second)
		connected = client.IsConnected()
	} else if !loggedIn && connected {
		// Disconnect stale connection if not logged in
		log.Println("Disconnecting stale connection")
		client.Disconnect()
		connected = false
	}

	c.JSON(http.StatusOK, StatusResponse{
		Connected: connected,
		LoggedIn:  loggedIn,
	})
}

func whatsappLogoutHandler(c *gin.Context) {
	client, err := getWhatsAppClient()
	if err != nil {
		c.JSON(http.StatusInternalServerError, SendResponse{Status: "error", Error: err.Error()})
		return
	}

	if client.IsConnected() {
		client.Disconnect()
	}
	if client.Store.ID != nil {
		if err := client.Store.Delete(context.Background()); err != nil {
			c.JSON(http.StatusInternalServerError, SendResponse{Status: "error", Error: "logout_failed: " + err.Error()})
			return
		}
	}
	c.JSON(http.StatusOK, SendResponse{Status: "logged_out", Message: "Session cleared"})
}



// getCurrentTimeHandler returns the current time in Indonesia Time (WIB, UTC+7)
func getCurrentTimeHandler(c *gin.Context) {
	currentTime := time.Now()
	c.JSON(http.StatusOK, gin.H{
		"current_time": currentTime.Format("2006-01-02 15:04:05"),
		"timezone":     "WIB (UTC+7)",
	})
}

func regencyHandler(c *gin.Context) {
    // Execute the SQL query to retrieve regency data
    rows, err := db.Query(`
        SELECT
            id,
            province_id,
            name
        FROM reg_regencies
				WHERE aktif = 1
        ORDER BY name ASC;
    `)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    defer rows.Close()

    var regencies []Kabupaten
    for rows.Next() {
        var kabupaten Kabupaten

        // Scan the row into the Kabupaten struct
        if err := rows.Scan(
            &kabupaten.Id,
            &kabupaten.ProvinceId,
            &kabupaten.Nama,
        ); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        // Append the scanned regency to the slice
        regencies = append(regencies, kabupaten)
    }

    // Check for errors from iterating over rows
    if err := rows.Err(); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    // Return the regencies as a JSON response
    c.JSON(http.StatusOK, regencies)
}

func districtHandler(c *gin.Context) {
    // Execute the SQL query to retrieve district data
		id := c.Param("regency_id")
    rows, err := db.Query(`
        SELECT
            id,
            regency_id,
            name
        FROM reg_districts
				WHERE regency_id = ?
        ORDER BY name ASC;
    `, id)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    defer rows.Close()

    var kecamatanList []Kecamatan
    for rows.Next() {
        var kecamatan Kecamatan

        // Scan the row into the Kecamatan struct
        if err := rows.Scan(
            &kecamatan.Id,
            &kecamatan.RegencyId,
            &kecamatan.Nama,
        ); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        // Append the scanned district to the slice
        kecamatanList = append(kecamatanList, kecamatan)
    }

    // Check for errors from iterating over rows
    if err := rows.Err(); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    // Return the districts as a JSON response
    c.JSON(http.StatusOK, kecamatanList)
}

func villageHandler(c *gin.Context) {
    // Execute the SQL query to retrieve village data
		id := c.Param("district_id")
    rows, err := db.Query(`
        SELECT
            id,
            district_id,
            name
        FROM reg_villages
				WHERE district_id = ?
        ORDER BY name ASC;
    `, id)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    defer rows.Close()

    var desaList []Desa
    for rows.Next() {
        var desa Desa

        // Scan the row into the Desa struct
        if err := rows.Scan(
            &desa.Id,
            &desa.DistrictId,
            &desa.Nama,
        ); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        // Append the scanned village to the slice
        desaList = append(desaList, desa)
    }

    // Check for errors from iterating over rows
    if err := rows.Err(); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    // Return the villages as a JSON response
    c.JSON(http.StatusOK, desaList)
}

func getAdministrativeData(c *gin.Context) {
    rows, err := db.Query(`
        SELECT
            r.id AS regency_id,
            r.name AS regency_name,
            d.id AS district_id,
            d.name AS district_name,
            v.id AS village_id,
            v.name AS village_name
        FROM reg_regencies r
        INNER JOIN reg_districts d ON r.id = d.regency_id
        LEFT JOIN reg_villages v ON d.id = v.district_id
        WHERE r.aktif = 1
        ORDER BY r.name, d.name, v.name
    `)

    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    defer rows.Close()


    type Village struct {
        Id   int    `json:"id"`
        Name string `json:"name"`
    }

    type District struct {
        Id      int      `json:"id"`
        Name    string   `json:"name"`
        Villages []Village `json:"villages"`
    }

    type Regency struct {
        Id        int        `json:"id"`
        Name      string     `json:"name"`
        Districts []District `json:"districts"`
    }

    regencies := make(map[int]Regency)
    for rows.Next() {
        var regencyId, districtId, villageId int
        var regencyName, districtName, villageName string
        err := rows.Scan(&regencyId, &regencyName, &districtId, &districtName, &villageId, &villageName)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        regency, ok := regencies[regencyId]
        if !ok {
            regency = Regency{Id: regencyId, Name: regencyName, Districts: []District{}}
        }

        districtIndex := -1
        for i, d := range regency.Districts {
            if d.Id == districtId {
                districtIndex = i
                break
            }
        }

        if districtIndex == -1 {
            district := District{Id: districtId, Name: districtName, Villages: []Village{}}
            regency.Districts = append(regency.Districts, district)
            districtIndex = len(regency.Districts) - 1
        }

        if villageId != 0 {
            village := Village{Id: villageId, Name: villageName}
            regency.Districts[districtIndex].Villages = append(regency.Districts[districtIndex].Villages, village)
        }
        regencies[regencyId] = regency
    }

    if err := rows.Err(); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    regencyList := make([]Regency, 0, len(regencies))
    for _, v := range regencies {
        regencyList = append(regencyList, v)
    }

    c.JSON(http.StatusOK, regencyList)
}

// testWhatsAppHandler sends a test WhatsApp message using existing sendWhatsAppMessage function
func testWhatsAppHandler(c *gin.Context) {
	var request struct {
		PhoneNumber string `json:"phone_number" binding:"required"`
		Message     string `json:"message" binding:"required"`
	}

	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Invalid request body",
			"details": err.Error(),
		})
		return
	}

	// Validate phone number
	if len(request.PhoneNumber) < 8 {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Phone number too short",
		})
		return
	}

	// Check WhatsApp status first
	client, err := getWhatsAppClient()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "WhatsApp client error",
			"details": err.Error(),
		})
		return
	}

	if client.Store.ID == nil {
		c.JSON(http.StatusPreconditionRequired, gin.H{
			"error": "WhatsApp not authenticated",
			"message": "Please scan QR code first using /whatsapp-qr endpoint",
		})
		return
	}

	// Send the message
	err = sendWhatsAppMessage(request.PhoneNumber, request.Message)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Failed to send WhatsApp message",
			"details": err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "WhatsApp message sent successfully",
		"phone_number": request.PhoneNumber,
		"sent_at": time.Now().Format("2006-01-02 15:04:05"),
	})
}






func checkWhatsAppStatusHandler(c *gin.Context) {
	client, err := getWhatsAppClient()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Failed to get WhatsApp client",
			"details": err.Error(),
		})
		return
	}

	status := gin.H{
		"client_initialized": true,
		"connected": client.IsConnected(),
		"authenticated": client.Store.ID != nil,
	}

	if client.Store.ID != nil {
		status["device_id"] = client.Store.ID.String()
		status["device_jid"] = client.Store.ID.String()
	}

	c.JSON(http.StatusOK, status)
}


// generateQRHandler creates a QR code in ASCII format for WhatsApp authentication
func generateQRHandler(c *gin.Context) {
	client, err := getWhatsAppClient()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Failed to initialize WhatsApp client",
			"details": err.Error(),
		})
		return
	}

	// Check if already authenticated
	if client.Store.ID != nil {
		c.JSON(http.StatusOK, gin.H{
			"authenticated": true,
			"message": "Already authenticated",
			"device_id": client.Store.ID.String(),
		})
		return
	}

	// If client is already connected, disconnect first
	if client.IsConnected() {
		client.Disconnect()
	}

	// Create context with longer timeout for QR generation
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	// Get QR channel before connecting
	qrChan, err := client.GetQRChannel(ctx)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Failed to get QR channel",
			"details": err.Error(),
		})
		return
	}

	// Connect the client
	err = client.Connect()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Failed to connect client",
			"details": err.Error(),
		})
		return
	}

	log.Printf("Waiting for QR code...")

	// Wait for events
	for {
		select {
		case evt := <-qrChan:
			switch evt.Event {
			case "code":
				log.Printf("Got QR code, generating response...")

				// Generate ASCII QR code
				asciiQR, err := generateASCIIQR(evt.Code)
				if err != nil {
					log.Printf("Failed to generate ASCII QR: %v", err)
					asciiQR = "Failed to generate ASCII QR code"
				}

				c.JSON(http.StatusOK, gin.H{
					"success": true,
					"message": "Scan this QR code with WhatsApp",
					"qr_ascii": asciiQR,
					"qr_data": evt.Code,
					"timeout": 60, // seconds
					"instructions": "1. Open WhatsApp on your phone\n2. Go to Settings > Linked Devices\n3. Tap 'Link a Device'\n4. Scan the QR code above",
				})
				return

			case "success":
				log.Printf("QR code scanned successfully!")
				c.JSON(http.StatusOK, gin.H{
					"success": true,
					"authenticated": true,
					"message": "QR code scanned successfully! Device is now linked.",
					"device_id": client.Store.ID.String(),
				})
				return

			case "timeout":
				log.Printf("QR code timed out")
				c.JSON(http.StatusRequestTimeout, gin.H{
					"success": false,
					"error": "QR code expired",
					"message": "The QR code has expired. Please try again.",
				})
				return

			case "error":
				log.Printf("QR code error: %v", evt.Error)
				c.JSON(http.StatusInternalServerError, gin.H{
					"success": false,
					"error": "QR code error",
					"details": evt.Error.Error(),
				})
				return
			}

		case <-ctx.Done():
			c.JSON(http.StatusRequestTimeout, gin.H{
				"success": false,
				"error": "Request timeout",
				"message": "QR code generation timed out. Please try again.",
			})
			return
		}
	}
}

// Alternative simpler ASCII QR generator (if the above doesn't work well)
func generateSimpleASCIIQR(data string) (string, error) {
	qr, err := qrcode.New(data, qrcode.Medium)
	if err != nil {
		return "", fmt.Errorf("failed to create QR code: %v", err)
	}

	bitmap := qr.Bitmap()
	var asciiQR strings.Builder

	for _, row := range bitmap {
		for _, pixel := range row {
			if pixel {
				asciiQR.WriteString("██") // Black pixel
			} else {
				asciiQR.WriteString("  ") // White pixel
			}
		}
		asciiQR.WriteString("\n")
	}

	return asciiQR.String(), nil
}
