package bkosmi

import (
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/playwright-community/playwright-go"
	"github.com/sirupsen/logrus"
)

// NativeClient uses Playwright to establish WebSocket, then interacts directly via JavaScript
type NativeClient struct {
	roomURL         string
	roomID          string
	log             *logrus.Entry
	pw              *playwright.Playwright
	browser         playwright.Browser
	page            playwright.Page
	messageCallback func(*NewMessagePayload)
	connected       bool
	mu              sync.RWMutex
}

// NewNativeClient creates a new native client with Playwright-assisted connection
func NewNativeClient(roomURL, roomID string, log *logrus.Entry) *NativeClient {
	return &NativeClient{
		roomURL: roomURL,
		roomID:  roomID,
		log:     log,
	}
}

// Connect launches Playwright and establishes the WebSocket connection
func (c *NativeClient) Connect() error {
	c.log.Info("Starting Playwright native client")

	// Launch Playwright
	pw, err := playwright.Run()
	if err != nil {
		return fmt.Errorf("failed to start Playwright: %w", err)
	}
	c.pw = pw

	// Launch browser with resource optimizations
	browser, err := pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{
		Headless: playwright.Bool(true),
		Args: []string{
			"--no-sandbox",
			"--disable-dev-shm-usage",
			"--disable-blink-features=AutomationControlled",
			
			// Resource optimizations for reduced CPU/memory usage
			"--disable-gpu",                    // No GPU needed for chat
			"--disable-software-rasterizer",    // No rendering needed
			"--disable-extensions",             // No extensions needed
			"--disable-background-networking",  // No background requests
			"--disable-background-timer-throttling",
			"--disable-backgrounding-occluded-windows",
			"--disable-breakpad",               // No crash reporting
			"--disable-component-extensions-with-background-pages",
			"--disable-features=TranslateUI",   // No translation UI
			"--disable-ipc-flooding-protection",
			"--disable-renderer-backgrounding",
			"--force-color-profile=srgb",
			"--metrics-recording-only",
			"--no-first-run",                   // Skip first-run tasks
			"--mute-audio",                     // No audio needed
		},
	})
	if err != nil {
		c.pw.Stop()
		return fmt.Errorf("failed to launch browser: %w", err)
	}
	c.browser = browser

	// Create context
	context, err := browser.NewContext(playwright.BrowserNewContextOptions{
		UserAgent: playwright.String("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"),
	})
	if err != nil {
		c.browser.Close()
		c.pw.Stop()
		return fmt.Errorf("failed to create context: %w", err)
	}

	// Create page
	page, err := context.NewPage()
	if err != nil {
		c.browser.Close()
		c.pw.Stop()
		return fmt.Errorf("failed to create page: %w", err)
	}
	c.page = page

	// Inject WebSocket interceptor
	c.log.Debug("Injecting WebSocket access layer")
	if err := c.injectWebSocketAccess(); err != nil {
		c.Disconnect()
		return fmt.Errorf("failed to inject WebSocket access: %w", err)
	}

	// Navigate to room
	c.log.Infof("Navigating to Kosmi room: %s", c.roomURL)
	if _, err := page.Goto(c.roomURL, playwright.PageGotoOptions{
		WaitUntil: playwright.WaitUntilStateDomcontentloaded, // Wait for DOM only, not all resources
	}); err != nil {
		c.Disconnect()
		return fmt.Errorf("failed to navigate: %w", err)
	}

	// Wait for WebSocket to establish
	c.log.Debug("Waiting for WebSocket connection")
	if err := c.waitForWebSocket(); err != nil {
		c.Disconnect()
		return fmt.Errorf("WebSocket not established: %w", err)
	}

	// Subscribe to room messages
	c.log.Debugf("Subscribing to messages in room %s", c.roomID)
	if err := c.subscribeToMessages(); err != nil {
		c.Disconnect()
		return fmt.Errorf("failed to subscribe: %w", err)
	}

	c.mu.Lock()
	c.connected = true
	c.mu.Unlock()

	c.log.Info("Native client connected successfully")

	// Start message listener
	go c.listenForMessages()

	return nil
}

// injectWebSocketAccess injects JavaScript that provides direct WebSocket access
func (c *NativeClient) injectWebSocketAccess() error {
	script := `
		(function() {
			if (window.__KOSMI_NATIVE_CLIENT__) return;
			
			const OriginalWebSocket = window.WebSocket;
			window.__KOSMI_WS__ = null;
			window.__KOSMI_MESSAGE_QUEUE__ = [];
			window.__KOSMI_READY__ = false;
			
			// Hook WebSocket constructor to capture the connection
			window.WebSocket = function(url, protocols) {
				const socket = new OriginalWebSocket(url, protocols);
				
				if (url.includes('engine.kosmi.io') || url.includes('gql-ws')) {
					window.__KOSMI_WS__ = socket;
					
					// Hook message handler to queue messages
					socket.addEventListener('message', (event) => {
						try {
							const data = JSON.parse(event.data);
							window.__KOSMI_MESSAGE_QUEUE__.push({
								timestamp: Date.now(),
								data: data
							});
						} catch (e) {
							// Ignore non-JSON messages
						}
					});
					
					// Mark as ready when connection opens
					socket.addEventListener('open', () => {
						window.__KOSMI_READY__ = true;
					});
				}
				
				return socket;
			};
			
			// Preserve WebSocket properties
			window.WebSocket.prototype = OriginalWebSocket.prototype;
			window.WebSocket.CONNECTING = OriginalWebSocket.CONNECTING;
			window.WebSocket.OPEN = OriginalWebSocket.OPEN;
			window.WebSocket.CLOSING = OriginalWebSocket.CLOSING;
			window.WebSocket.CLOSED = OriginalWebSocket.CLOSED;
			
			window.__KOSMI_NATIVE_CLIENT__ = true;
		})();
	`

	return c.page.AddInitScript(playwright.Script{
		Content: playwright.String(script),
	})
}

// waitForWebSocket waits for the WebSocket to be established
func (c *NativeClient) waitForWebSocket() error {
	for i := 0; i < 30; i++ { // 15 seconds max
		result, err := c.page.Evaluate(`
			(function() {
				return {
					ready: !!window.__KOSMI_READY__,
					wsExists: !!window.__KOSMI_WS__,
					wsState: window.__KOSMI_WS__ ? window.__KOSMI_WS__.readyState : -1
				};
			})();
		`)
		if err != nil {
			return err
		}

		status := result.(map[string]interface{})
		ready := status["ready"].(bool)

		if ready {
			c.log.Info("✅ WebSocket is ready")
			return nil
		}

		if i%5 == 0 {
			c.log.Debugf("Waiting for WebSocket... (attempt %d/30)", i+1)
		}

		time.Sleep(500 * time.Millisecond)
	}

	return fmt.Errorf("timeout waiting for WebSocket")
}

// subscribeToMessages subscribes to room messages via the WebSocket
func (c *NativeClient) subscribeToMessages() error {
	script := fmt.Sprintf(`
		(function() {
			if (!window.__KOSMI_WS__ || window.__KOSMI_WS__.readyState !== WebSocket.OPEN) {
				return { success: false, error: 'WebSocket not ready' };
			}
			
			const subscription = {
				id: 'native-client-subscription',
				type: 'subscribe',
				payload: {
					query: 'subscription { newMessage(roomId: "%s") { body time user { displayName username } } }',
					variables: {}
				}
			};
			
			try {
				window.__KOSMI_WS__.send(JSON.stringify(subscription));
				return { success: true };
			} catch (e) {
				return { success: false, error: e.toString() };
			}
		})();
	`, c.roomID)

	result, err := c.page.Evaluate(script)
	if err != nil {
		return err
	}

	response := result.(map[string]interface{})
	if success, ok := response["success"].(bool); !ok || !success {
		errMsg := "unknown error"
		if e, ok := response["error"].(string); ok {
			errMsg = e
		}
		return fmt.Errorf("subscription failed: %s", errMsg)
	}

	return nil
}

// listenForMessages continuously polls for new messages
func (c *NativeClient) listenForMessages() {
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()

	for {
		c.mu.RLock()
		if !c.connected {
			c.mu.RUnlock()
			return
		}
		c.mu.RUnlock()

		if err := c.pollMessages(); err != nil {
			c.log.Errorf("Error polling messages: %v", err)
		}

		<-ticker.C
	}
}

// pollMessages retrieves and processes messages from the queue
func (c *NativeClient) pollMessages() error {
	result, err := c.page.Evaluate(`
		(function() {
			if (!window.__KOSMI_MESSAGE_QUEUE__) return null;
			if (window.__KOSMI_MESSAGE_QUEUE__.length === 0) return null;
			const messages = window.__KOSMI_MESSAGE_QUEUE__.slice();
			window.__KOSMI_MESSAGE_QUEUE__ = [];
			return messages;
		})();
	`)
	if err != nil {
		return err
	}

	// Early return if no messages (reduces CPU during idle)
	if result == nil {
		return nil
	}

	messagesJSON, err := json.Marshal(result)
	if err != nil {
		return err
	}

	var messages []struct {
		Timestamp int64                  `json:"timestamp"`
		Data      map[string]interface{} `json:"data"`
	}

	if err := json.Unmarshal(messagesJSON, &messages); err != nil {
		return err
	}

	for _, msg := range messages {
		c.processMessage(msg.Data)
	}

	return nil
}

// processMessage processes a single WebSocket message
func (c *NativeClient) processMessage(data map[string]interface{}) {
	msgType, ok := data["type"].(string)
	if !ok || msgType != "next" {
		return
	}

	payload, ok := data["payload"].(map[string]interface{})
	if !ok {
		return
	}

	dataField, ok := payload["data"].(map[string]interface{})
	if !ok {
		return
	}

	newMessage, ok := dataField["newMessage"].(map[string]interface{})
	if !ok {
		return
	}

	// Parse into our struct
	jsonBytes, err := json.Marshal(map[string]interface{}{
		"data": map[string]interface{}{
			"newMessage": newMessage,
		},
	})
	if err != nil {
		return
	}

	var msgPayload NewMessagePayload
	if err := json.Unmarshal(jsonBytes, &msgPayload); err != nil {
		return
	}

	if c.messageCallback != nil {
		c.messageCallback(&msgPayload)
	}
}

// SendMessage sends a message by typing into the Kosmi chat input field
func (c *NativeClient) SendMessage(text string) error {
	c.mu.RLock()
	if !c.connected {
		c.mu.RUnlock()
		return fmt.Errorf("not connected")
	}
	c.mu.RUnlock()

	c.log.Debugf("Sending message to Kosmi: %s", text)

	// Escape the message text for JavaScript
	textJSON, _ := json.Marshal(text)
	
	script := fmt.Sprintf(`
		(async function() {
			try {
				// Try multiple strategies to find the chat input
				let input = null;
				
				// Strategy 1: Look for textarea
				const textareas = document.querySelectorAll('textarea');
				for (let ta of textareas) {
					if (ta.offsetParent !== null) {  // visible
						input = ta;
						break;
					}
				}
				
				// Strategy 2: Look for contenteditable
				if (!input) {
					const editables = document.querySelectorAll('[contenteditable="true"]');
					for (let ed of editables) {
						if (ed.offsetParent !== null) {  // visible
							input = ed;
							break;
						}
					}
				}
				
				// Strategy 3: Look for input text
				if (!input) {
					const inputs = document.querySelectorAll('input[type="text"]');
					for (let inp of inputs) {
						if (inp.offsetParent !== null) {  // visible
							input = inp;
							break;
						}
					}
				}
				
				if (!input) {
					return { success: false, error: 'Could not find any visible input element' };
				}
				
				// Set the value based on element type
				if (input.contentEditable === 'true') {
					input.textContent = %s;
					input.dispatchEvent(new Event('input', { bubbles: true }));
				} else {
					input.value = %s;
					input.dispatchEvent(new Event('input', { bubbles: true }));
					input.dispatchEvent(new Event('change', { bubbles: true }));
				}
				
				// Focus the input
				input.focus();
				
				// Wait a tiny bit
				await new Promise(resolve => setTimeout(resolve, 100));
				
				// Find and click the send button, or press Enter
				const sendButton = document.querySelector('button[type="submit"], button[class*="send" i], button[aria-label*="send" i]');
				if (sendButton && sendButton.offsetParent !== null) {
					sendButton.click();
				} else {
					// Simulate Enter key press
					const enterEvent = new KeyboardEvent('keydown', {
						key: 'Enter',
						code: 'Enter',
						keyCode: 13,
						which: 13,
						bubbles: true,
						cancelable: true
					});
					input.dispatchEvent(enterEvent);
				}
				
				return { success: true };
			} catch (e) {
				return { success: false, error: e.toString() };
			}
		})();
	`, string(textJSON), string(textJSON))

	result, err := c.page.Evaluate(script)
	if err != nil {
		c.log.Errorf("Failed to execute send script: %v", err)
		return fmt.Errorf("failed to execute send: %w", err)
	}

	response := result.(map[string]interface{})
	if success, ok := response["success"].(bool); !ok || !success {
		errMsg := "unknown error"
		if e, ok := response["error"].(string); ok {
			errMsg = e
		}
		c.log.Errorf("Send failed: %s", errMsg)
		return fmt.Errorf("send failed: %s", errMsg)
	}

	c.log.Debug("Successfully sent message to Kosmi")
	return nil
}

// OnMessage registers a callback for incoming messages
func (c *NativeClient) OnMessage(callback func(*NewMessagePayload)) {
	c.messageCallback = callback
}

// Disconnect closes the Playwright browser
func (c *NativeClient) Disconnect() error {
	c.mu.Lock()
	c.connected = false
	c.mu.Unlock()

	c.log.Debug("Closing Playwright browser")

	if c.browser != nil {
		c.browser.Close()
	}

	if c.pw != nil {
		c.pw.Stop()
	}

	return nil
}

// IsConnected returns whether the client is connected
func (c *NativeClient) IsConnected() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.connected
}

