package main

import (
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"time"

	"github.com/gorilla/websocket"
)

const (
	userAgent  = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
	appVersion = "4364"
)

func main() {
	roomURL := flag.String("room", "https://app.kosmi.io/room/@hyperspaceout", "Full Kosmi room URL")
	token := flag.String("token", "", "JWT token (optional, will try to extract from page)")
	flag.Parse()

	fmt.Println("🌐 Testing session-based WebSocket connection")
	fmt.Printf("   Room URL: %s\n\n", *roomURL)

	// Create HTTP client with cookie jar
	jar, err := cookiejar.New(nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create cookie jar: %v\n", err)
		os.Exit(1)
	}

	client := &http.Client{
		Jar:     jar,
		Timeout: 30 * time.Second,
	}

	// Step 1: Visit the room page to establish session
	fmt.Println("1️⃣  Visiting room page to establish session...")
	if err := visitRoomPage(client, *roomURL); err != nil {
		fmt.Fprintf(os.Stderr, "❌ Failed to visit room: %v\n", err)
		os.Exit(1)
	}
	fmt.Println("✅ Session established!")

	// Print cookies
	u, _ := url.Parse(*roomURL)
	cookies := client.Jar.Cookies(u)
	fmt.Printf("\n   📋 Cookies received: %d\n", len(cookies))
	for _, c := range cookies {
		fmt.Printf("      - %s=%s\n", c.Name, truncate(c.Value, 50))
	}

	// Step 2: Connect WebSocket with cookies
	fmt.Println("\n2️⃣  Connecting WebSocket with session cookies...")
	
	roomID := extractRoomID(*roomURL)
	conn, err := connectWebSocketWithSession(client.Jar, *token, roomID)
	if err != nil {
		fmt.Fprintf(os.Stderr, "❌ Failed to connect WebSocket: %v\n", err)
		os.Exit(1)
	}
	defer conn.Close()
	fmt.Println("✅ WebSocket connected!")

	// Step 3: Listen for messages
	fmt.Println("\n👂 Listening for messages (press Ctrl+C to exit)...\n")
	
	messageCount := 0
	for {
		var msg map[string]interface{}
		if err := conn.ReadJSON(&msg); err != nil {
			fmt.Fprintf(os.Stderr, "Error reading message: %v\n", err)
			break
		}

		msgType, _ := msg["type"].(string)
		
		switch msgType {
		case "next":
			payload, _ := msg["payload"].(map[string]interface{})
			data, _ := payload["data"].(map[string]interface{})
			
			if newMessage, ok := data["newMessage"].(map[string]interface{}); ok {
				messageCount++
				body, _ := newMessage["body"].(string)
				user, _ := newMessage["user"].(map[string]interface{})
				username, _ := user["displayName"].(string)
				if username == "" {
					username, _ = user["username"].(string)
				}
				timestamp, _ := newMessage["time"].(float64)
				
				t := time.Unix(int64(timestamp), 0)
				fmt.Printf("[%s] %s: %s\n", t.Format("15:04:05"), username, body)
			}
		case "connection_ack":
			fmt.Println("   ✅ Received connection_ack")
		case "complete":
			id, _ := msg["id"].(string)
			fmt.Printf("   [Subscription %s completed]\n", id)
		case "error":
			fmt.Printf("   ⚠️  Error: %+v\n", msg)
		case "ka":
			// Keep-alive, ignore
		default:
			fmt.Printf("   📨 %s\n", msgType)
		}
	}

	fmt.Printf("\n📊 Total messages received: %d\n", messageCount)
}

func visitRoomPage(client *http.Client, roomURL string) error {
	req, err := http.NewRequest("GET", roomURL, nil)
	if err != nil {
		return err
	}

	req.Header.Set("User-Agent", userAgent)
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req.Header.Set("Accept-Language", "en-US,en;q=0.9")

	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return fmt.Errorf("status %d", resp.StatusCode)
	}

	// Read and discard body (but process Set-Cookie headers)
	io.Copy(io.Discard, resp.Body)
	
	return nil
}

func connectWebSocketWithSession(jar http.CookieJar, token, roomID string) (*websocket.Conn, error) {
	dialer := websocket.Dialer{
		Jar:             jar,
		Subprotocols:    []string{"graphql-ws"},
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}
	
	headers := http.Header{
		"Origin":     []string{"https://app.kosmi.io"},
		"User-Agent": []string{userAgent},
	}
	
	conn, resp, err := dialer.Dial("wss://engine.kosmi.io/gql-ws", headers)
	if err != nil {
		if resp != nil {
			fmt.Printf("   Response status: %d\n", resp.StatusCode)
			// Print response headers
			fmt.Println("   Response headers:")
			for k, v := range resp.Header {
				fmt.Printf("      %s: %v\n", k, v)
			}
		}
		return nil, err
	}

	// Send connection_init
	// If token not provided, try without it
	uaEncoded := base64.StdEncoding.EncodeToString([]byte(userAgent))
	
	payload := map[string]interface{}{
		"ua": uaEncoded,
		"v":  appVersion,
		"r":  "",
	}
	
	if token != "" {
		payload["token"] = token
	}
	
	initMsg := map[string]interface{}{
		"type":    "connection_init",
		"payload": payload,
	}

	if err := conn.WriteJSON(initMsg); err != nil {
		conn.Close()
		return nil, fmt.Errorf("failed to send connection_init: %w", err)
	}

	// Wait for ack
	var ackMsg map[string]interface{}
	if err := conn.ReadJSON(&ackMsg); err != nil {
		conn.Close()
		return nil, fmt.Errorf("failed to read ack: %w", err)
	}

	msgType, _ := ackMsg["type"].(string)
	if msgType != "connection_ack" {
		conn.Close()
		return nil, fmt.Errorf("expected connection_ack, got %s", msgType)
	}

	// Subscribe to messages
	query := fmt.Sprintf(`
		subscription {
			newMessage(roomId: "%s") {
				body
				time
				user {
					displayName
					username
				}
			}
		}
	`, roomID)

	subMsg := map[string]interface{}{
		"id":   "newMessage-subscription",
		"type": "subscribe",
		"payload": map[string]interface{}{
			"query":     query,
			"variables": map[string]interface{}{},
		},
	}

	if err := conn.WriteJSON(subMsg); err != nil {
		conn.Close()
		return nil, fmt.Errorf("failed to subscribe: %w", err)
	}

	return conn, nil
}

func extractRoomID(roomURL string) string {
	// Extract room ID from URL
	// https://app.kosmi.io/room/@roomname -> @roomname
	// https://app.kosmi.io/room/roomid -> roomid
	parts := make([]string, 0)
	for _, part := range []byte(roomURL) {
		if part == '/' {
			parts = append(parts, "")
		} else if len(parts) > 0 {
			parts[len(parts)-1] += string(part)
		}
	}
	
	if len(parts) > 0 {
		return parts[len(parts)-1]
	}
	return roomURL
}

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

