package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	_ "github.com/mattn/go-sqlite3"
	"github.com/skip2/go-qrcode"
	"go.mau.fi/whatsmeow"
	waE2E "go.mau.fi/whatsmeow/binary/proto"
	waLog "go.mau.fi/whatsmeow/util/log"
	"go.mau.fi/whatsmeow/store/sqlstore"
	"go.mau.fi/whatsmeow/types"
	"go.mau.fi/whatsmeow/types/events"
)

type API struct {
	client *whatsmeow.Client
	db     *sql.DB
}

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"`
}

// 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
}

func main() {
	// Initialize DB
	db, err := sql.Open("sqlite3", "whatsapp.db?_foreign_keys=on")
	if err != nil {
		log.Fatal("Failed to open DB:", err)
	}
	defer db.Close()

	// Setup logger
	logger := waLog.Stdout("wa", "DEBUG", true) // Set to DEBUG for detailed logs

	// Setup SQL store with logger
	container, err := sqlstore.New(context.Background(), "sqlite3", "whatsapp.db?_foreign_keys=on", logger)
	if err != nil {
		log.Fatal("Failed to create SQL store:", err)
	}

	// Get or create device
	device, err := container.GetFirstDevice(context.Background())
	if err != nil {
		log.Fatal("Failed to get device:", 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
		}
	})

	api := &API{
		client: client,
		db:     db,
	}

	r := gin.Default()
	r.GET("/login", api.login)
	r.POST("/send", api.sendMessage)
	r.GET("/status", api.getStatus)
	r.POST("/logout", api.logout)

	log.Println("Server starting on :8080")
	r.Run(":8080")
}

func (a *API) login(c *gin.Context) {
	if a.client.IsLoggedIn() {
		c.JSON(http.StatusOK, LoginResponse{Status: "already_logged_in"})
		return
	}

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

	qrChan, _ := a.client.GetQRChannel(context.Background())
	if err := a.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 (a *API) sendMessage(c *gin.Context) {
	var req SendRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, SendResponse{Status: "error", Error: err.Error()})
		return
	}

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

	// Normalize phone number
	normalizedTo, err := normalizePhoneNumber(req.To)
	if err != nil {
		c.JSON(http.StatusBadRequest, SendResponse{Status: "error", Error: err.Error()})
		return
	}

	// Force reconnect to ensure fresh session
	if a.client.IsConnected() {
		log.Println("Disconnecting to ensure fresh session for send")
		a.client.Disconnect()
		time.Sleep(1 * time.Second)
	}
	if err := a.client.Connect(); err != nil {
		c.JSON(http.StatusServiceUnavailable, SendResponse{Status: "error", Error: "reconnect_failed: " + err.Error()})
		return
	}

	// Parse JID with normalized number
	jid := types.NewJID(normalizedTo, types.DefaultUserServer)
	msg := &waE2E.Message{
		Conversation: &req.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 := a.client.SendMessage(ctx, jid, msg)
		cancel()

		if err == nil {
			c.JSON(http.StatusOK, SendResponse{Status: "sent", Message: "Message sent to " + normalizedTo})
			return
		}

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

	// All retries failed
	errorMsg := fmt.Sprintf("send_failed_after_retries: %v", sendErr)
	c.JSON(http.StatusInternalServerError, SendResponse{Status: "error", Error: errorMsg})
}

func (a *API) getStatus(c *gin.Context) {
	connected := a.client.IsConnected()
	loggedIn := a.client.IsLoggedIn()

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

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

func (a *API) logout(c *gin.Context) {
	if a.client.IsConnected() {
		a.client.Disconnect()
	}
	if a.client.Store.ID != nil {
		if err := a.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"})
}
