package main

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

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

type Message struct {
	ID            string    `json:"id"`
	Platform      string    `json:"platform"`
	From          string    `json:"from"`
	FromName      string    `json:"from_name,omitempty"`
	To            string    `json:"to,omitempty"`
	Body          string    `json:"body"`
	Timestamp     time.Time `json:"timestamp"`
	IsGroup       bool      `json:"is_group"`
	GroupName     string    `json:"group_name,omitempty"`
	MediaType     string    `json:"media_type,omitempty"`
	MediaURL      string    `json:"media_url,omitempty"`
	MediaPath     string    `json:"media_path,omitempty"`
	HasMedia      bool      `json:"has_media"`
	Transcription string    `json:"transcription,omitempty"`
}

type MessageBridge struct {
	waClient      *whatsmeow.Client
	messages      []Message
	msgMutex      sync.RWMutex
	dataDir       string
	webhookURL    string
	webhookToken  string
	qrChan        chan string
	connected     bool
	webhooksSent  map[string]bool // Track sent webhook message IDs
	webhooksMutex sync.Mutex
}

var bridge *MessageBridge

func NewMessageBridge(dataDir string) *MessageBridge {
	return &MessageBridge{
		dataDir:      dataDir,
		messages:     make([]Message, 0),
		qrChan:       make(chan string, 1),
		webhookURL:   os.Getenv("WEBHOOK_URL"),
		webhookToken: os.Getenv("WEBHOOK_TOKEN"),
		webhooksSent: make(map[string]bool),
	}
}

func (b *MessageBridge) InitWhatsApp() error {
	ctx := context.Background()
	dbPath := filepath.Join(b.dataDir, "whatsapp.db")
	container, err := sqlstore.New(ctx, "sqlite3", "file:"+dbPath+"?_foreign_keys=on", waLog.Noop)
	if err != nil {
		return fmt.Errorf("failed to create store: %w", err)
	}

	deviceStore, err := container.GetFirstDevice(ctx)
	if err != nil {
		return fmt.Errorf("failed to get device: %w", err)
	}

	b.waClient = whatsmeow.NewClient(deviceStore, waLog.Noop)
	b.waClient.AddEventHandler(b.handleWhatsAppEvent)

	if b.waClient.Store.ID == nil {
		// Need to pair
		qrChan, _ := b.waClient.GetQRChannel(ctx)
		err = b.waClient.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect: %w", err)
		}

		go func() {
			for evt := range qrChan {
				if evt.Event == "code" {
					select {
					case b.qrChan <- evt.Code:
					default:
					}
				}
			}
		}()
	} else {
		err = b.waClient.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect: %w", err)
		}
		b.connected = true
	}

	return nil
}

func (b *MessageBridge) handleWhatsAppEvent(evt interface{}) {
	switch v := evt.(type) {
	case *events.Connected:
		b.connected = true
		log.Println("WhatsApp connected")

	case *events.Message:
		msg := b.parseWhatsAppMessage(v)
		if msg != nil {
			b.addMessage(*msg)
			b.sendWebhook(*msg)
		}
	}
}

func (b *MessageBridge) parseWhatsAppMessage(evt *events.Message) *Message {
	info := evt.Info

	msg := &Message{
		ID:        info.ID,
		Platform:  "whatsapp",
		From:      info.Sender.User,
		Timestamp: info.Timestamp,
		IsGroup:   info.IsGroup,
	}

	// Get sender name
	if info.PushName != "" {
		msg.FromName = info.PushName
	}

	// Get group info
	if info.IsGroup {
		msg.To = info.Chat.User
		// Try to get group name from cache
		ctx := context.Background()
		groupInfo, err := b.waClient.GetGroupInfo(ctx, info.Chat)
		if err == nil {
			msg.GroupName = groupInfo.Name
		}
	}

	// Parse message content
	if evt.Message != nil {
		if evt.Message.GetConversation() != "" {
			msg.Body = evt.Message.GetConversation()
		} else if evt.Message.GetExtendedTextMessage() != nil {
			msg.Body = evt.Message.GetExtendedTextMessage().GetText()
		} else if img := evt.Message.GetImageMessage(); img != nil {
			msg.HasMedia = true
			msg.MediaType = "image"
			msg.Body = img.GetCaption()
			b.downloadMedia(evt, msg)
		} else if vid := evt.Message.GetVideoMessage(); vid != nil {
			msg.HasMedia = true
			msg.MediaType = "video"
			msg.Body = vid.GetCaption()
			b.downloadMedia(evt, msg)
		} else if audio := evt.Message.GetAudioMessage(); audio != nil {
			msg.HasMedia = true
			if audio.GetPTT() {
				msg.MediaType = "voice"
			} else {
				msg.MediaType = "audio"
			}
			b.downloadMedia(evt, msg)
		} else if doc := evt.Message.GetDocumentMessage(); doc != nil {
			msg.HasMedia = true
			msg.MediaType = "document"
			msg.Body = doc.GetFileName()
			b.downloadMedia(evt, msg)
		}
	}

	return msg
}

func (b *MessageBridge) downloadMedia(evt *events.Message, msg *Message) {
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	// Get the downloadable message
	var downloadable whatsmeow.DownloadableMessage
	var mimeType string
	if img := evt.Message.GetImageMessage(); img != nil {
		downloadable = img
		mimeType = img.GetMimetype()
	} else if vid := evt.Message.GetVideoMessage(); vid != nil {
		downloadable = vid
		mimeType = vid.GetMimetype()
	} else if audio := evt.Message.GetAudioMessage(); audio != nil {
		downloadable = audio
		mimeType = audio.GetMimetype()
	} else if doc := evt.Message.GetDocumentMessage(); doc != nil {
		downloadable = doc
		mimeType = doc.GetMimetype()
	}

	if downloadable == nil {
		return
	}

	data, err := b.waClient.Download(ctx, downloadable)
	if err != nil {
		log.Printf("Failed to download media: %v", err)
		return
	}

	// Save to file
	mediaDir := filepath.Join(b.dataDir, "media")
	os.MkdirAll(mediaDir, 0755)

	ext := getExtFromMime(mimeType, msg.MediaType, msg.Body)
	filename := fmt.Sprintf("%s_%s%s", msg.Platform, msg.ID, ext)
	filePath := filepath.Join(mediaDir, filename)

	if err := os.WriteFile(filePath, data, 0644); err != nil {
		log.Printf("Failed to save media: %v", err)
		return
	}

	msg.MediaURL = "/media/" + filename
	msg.MediaPath = filePath
	log.Printf("Saved media: %s (%d bytes, %s)", filePath, len(data), mimeType)

	// Transcribe voice messages
	if msg.MediaType == "voice" {
		b.transcribeAudio(msg)
	}
}

// transcribeAudio calls the transcription script for voice messages
func (b *MessageBridge) transcribeAudio(msg *Message) {
	transcriptScript := os.Getenv("TRANSCRIBE_SCRIPT")
	if transcriptScript == "" {
		transcriptScript = "/home/johan/clawd/scripts/transcribe-audio.sh"
	}

	// Check if script exists and GROQ_API_KEY is set
	if _, err := os.Stat(transcriptScript); os.IsNotExist(err) {
		log.Printf("Transcription script not found: %s", transcriptScript)
		return
	}
	if os.Getenv("GROQ_API_KEY") == "" {
		log.Printf("GROQ_API_KEY not set, skipping transcription")
		return
	}

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	cmd := exec.CommandContext(ctx, transcriptScript, msg.MediaPath)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		log.Printf("Transcription failed: %v (stderr: %s)", err, stderr.String())
		return
	}

	transcription := strings.TrimSpace(stdout.String())
	if transcription != "" {
		msg.Transcription = transcription
		log.Printf("Transcribed voice message: %s", truncate(transcription, 100))
	}
}

func getExtFromMime(mimeType, mediaType, body string) string {
	// Try mime type first
	switch {
	case strings.HasPrefix(mimeType, "image/jpeg"):
		return ".jpg"
	case strings.HasPrefix(mimeType, "image/png"):
		return ".png"
	case strings.HasPrefix(mimeType, "image/webp"):
		return ".webp"
	case strings.HasPrefix(mimeType, "image/gif"):
		return ".gif"
	case strings.HasPrefix(mimeType, "video/mp4"):
		return ".mp4"
	case strings.HasPrefix(mimeType, "audio/ogg"):
		return ".ogg"
	case strings.HasPrefix(mimeType, "audio/mpeg"):
		return ".mp3"
	case strings.HasPrefix(mimeType, "audio/mp4"):
		return ".m4a"
	case strings.HasPrefix(mimeType, "application/pdf"):
		return ".pdf"
	}

	// Fallback to media type
	switch mediaType {
	case "image":
		return ".jpg"
	case "video":
		return ".mp4"
	case "audio", "voice":
		return ".ogg"
	case "document":
		if body != "" {
			if ext := filepath.Ext(body); ext != "" {
				return ext
			}
		}
		return ".bin"
	}
	return ".bin"
}

func (b *MessageBridge) addMessage(msg Message) {
	b.msgMutex.Lock()
	defer b.msgMutex.Unlock()

	b.messages = append(b.messages, msg)

	// Keep last 1000 messages in memory
	if len(b.messages) > 1000 {
		b.messages = b.messages[len(b.messages)-1000:]
	}

	log.Printf("[%s] %s (%s): %s", msg.Platform, msg.FromName, msg.From, truncate(msg.Body, 50))
}

func (b *MessageBridge) sendWebhook(msg Message) {
	if b.webhookURL == "" {
		return
	}

	// Check if we've already sent a webhook for this message
	b.webhooksMutex.Lock()
	if b.webhooksSent[msg.ID] {
		b.webhooksMutex.Unlock()
		return
	}
	b.webhooksSent[msg.ID] = true
	// Prune old entries to prevent memory leak (keep last 1000)
	if len(b.webhooksSent) > 1000 {
		b.webhooksSent = make(map[string]bool)
		b.webhooksSent[msg.ID] = true
	}
	b.webhooksMutex.Unlock()

	payload, _ := json.Marshal(map[string]interface{}{
		"event":   "new_message",
		"message": msg,
	})

	go func() {
		req, err := http.NewRequest("POST", b.webhookURL, strings.NewReader(string(payload)))
		if err != nil {
			log.Printf("Webhook request error: %v", err)
			return
		}
		req.Header.Set("Content-Type", "application/json")
		if b.webhookToken != "" {
			req.Header.Set("Authorization", "Bearer "+b.webhookToken)
		}

		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			log.Printf("Webhook send error: %v", err)
			return
		}
		defer resp.Body.Close()

		if resp.StatusCode >= 400 {
			log.Printf("Webhook error response: %d", resp.StatusCode)
		} else {
			log.Printf("Webhook sent: %s from %s", msg.Platform, msg.From)
		}
	}()
}

func truncate(s string, max int) string {
	if len(s) <= max {
		return s
	}
	return s[:max] + "..."
}

// HTTP Handlers
func handleStatus(w http.ResponseWriter, r *http.Request) {
	status := map[string]interface{}{
		"whatsapp": map[string]interface{}{
			"connected": bridge.connected,
			"needs_qr":  bridge.waClient != nil && bridge.waClient.Store.ID == nil,
		},
		"message_count": len(bridge.messages),
	}
	json.NewEncoder(w).Encode(status)
}

func handleQR(w http.ResponseWriter, r *http.Request) {
	format := r.URL.Query().Get("format")
	
	select {
	case code := <-bridge.qrChan:
		// Put it back for other requests
		select {
		case bridge.qrChan <- code:
		default:
		}

		if format == "png" {
			png, err := qrcode.Encode(code, qrcode.Medium, 256)
			if err != nil {
				http.Error(w, err.Error(), 500)
				return
			}
			w.Header().Set("Content-Type", "image/png")
			w.Write(png)
		} else if format == "base64" {
			png, err := qrcode.Encode(code, qrcode.Medium, 256)
			if err != nil {
				http.Error(w, err.Error(), 500)
				return
			}
			json.NewEncoder(w).Encode(map[string]string{
				"qr_base64": base64.StdEncoding.EncodeToString(png),
				"qr_raw":    code,
			})
		} else {
			// Terminal QR
			qr, _ := qrcode.New(code, qrcode.Medium)
			w.Header().Set("Content-Type", "text/plain")
			w.Write([]byte(qr.ToSmallString(false)))
		}
	default:
		if bridge.connected {
			json.NewEncoder(w).Encode(map[string]string{"status": "already_connected"})
		} else {
			json.NewEncoder(w).Encode(map[string]string{"status": "no_qr_available"})
		}
	}
}

func handleMessages(w http.ResponseWriter, r *http.Request) {
	bridge.msgMutex.RLock()
	defer bridge.msgMutex.RUnlock()

	platform := r.URL.Query().Get("platform")
	limit := 50

	var filtered []Message
	for i := len(bridge.messages) - 1; i >= 0 && len(filtered) < limit; i-- {
		msg := bridge.messages[i]
		if platform == "" || msg.Platform == platform {
			filtered = append(filtered, msg)
		}
	}

	json.NewEncoder(w).Encode(filtered)
}

func handleMedia(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	filename := vars["filename"]
	
	mediaPath := filepath.Join(bridge.dataDir, "media", filename)
	http.ServeFile(w, r, mediaPath)
}

func handleSend(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Platform string `json:"platform"`
		To       string `json:"to"`
		Message  string `json:"message"`
	}
	
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), 400)
		return
	}

	if req.Platform == "whatsapp" && bridge.waClient != nil && bridge.connected {
		jid := types.NewJID(req.To, types.DefaultUserServer)
		_, err := bridge.waClient.SendMessage(context.Background(), jid, &waE2E.Message{
			Conversation: &req.Message,
		})
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}
		json.NewEncoder(w).Encode(map[string]string{"status": "sent"})
	} else {
		http.Error(w, "platform not connected", 400)
	}
}

func main() {
	dataDir := os.Getenv("DATA_DIR")
	if dataDir == "" {
		dataDir = filepath.Join(os.Getenv("HOME"), ".message-bridge")
	}
	os.MkdirAll(dataDir, 0755)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8030"
	}

	bridge = NewMessageBridge(dataDir)

	// Initialize WhatsApp
	log.Println("Initializing WhatsApp...")
	if err := bridge.InitWhatsApp(); err != nil {
		log.Printf("WhatsApp init error: %v", err)
	}

	// HTTP server
	r := mux.NewRouter()
	r.HandleFunc("/status", handleStatus).Methods("GET")
	r.HandleFunc("/qr", handleQR).Methods("GET")
	r.HandleFunc("/messages", handleMessages).Methods("GET")
	r.HandleFunc("/media/{filename}", handleMedia).Methods("GET")
	r.HandleFunc("/send", handleSend).Methods("POST")

	srv := &http.Server{
		Addr:    ":" + port,
		Handler: r,
	}

	go func() {
		log.Printf("Message Bridge listening on :%s", port)
		if err := srv.ListenAndServe(); err != http.ErrServerClosed {
			log.Fatal(err)
		}
	}()

	// Wait for shutdown
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	<-c

	log.Println("Shutting down...")
	if bridge.waClient != nil {
		bridge.waClient.Disconnect()
	}
	srv.Shutdown(context.Background())
}
