package bkosmi

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
	"github.com/sirupsen/logrus"
)

// HybridClient uses ChromeDP for auth/cookies and GraphQL for sending messages
type HybridClient struct {
	roomURL         string
	roomID          string
	log             *logrus.Entry
	ctx             context.Context
	cancel          context.CancelFunc
	httpClient      *http.Client
	messageCallback func(*NewMessagePayload)
	connected       bool
	mu              sync.RWMutex
}

// NewHybridClient creates a new hybrid client
func NewHybridClient(roomURL string, log *logrus.Entry) *HybridClient {
	return &HybridClient{
		roomURL: roomURL,
		log:     log,
	}
}

// Connect launches Chrome, gets cookies, and sets up GraphQL client
func (c *HybridClient) Connect() error {
	c.log.Info("Launching Chrome to obtain session cookies")

	// Extract room ID
	roomID, err := extractRoomID(c.roomURL)
	if err != nil {
		return fmt.Errorf("failed to extract room ID: %w", err)
	}
	c.roomID = roomID

	// Create Chrome context with anti-detection
	opts := append(chromedp.DefaultExecAllocatorOptions[:],
		chromedp.Flag("headless", true),
		chromedp.Flag("disable-gpu", false),
		chromedp.Flag("no-sandbox", true),
		chromedp.Flag("disable-dev-shm-usage", true),
		chromedp.Flag("disable-blink-features", "AutomationControlled"),
		chromedp.Flag("disable-infobars", true),
		chromedp.Flag("window-size", "1920,1080"),
		chromedp.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"),
	)

	allocCtx, allocCancel := chromedp.NewExecAllocator(context.Background(), opts...)
	ctx, cancel := chromedp.NewContext(allocCtx)

	c.ctx = ctx
	c.cancel = func() {
		cancel()
		allocCancel()
	}

	// Inject scripts to run on every new document BEFORE creating any pages
	// This ensures they run BEFORE any page JavaScript
	c.log.Info("Injecting scripts to run on every page load...")
	
	antiDetectionScript := `
		Object.defineProperty(navigator, 'webdriver', { get: () => false });
		Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
		Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
		window.chrome = { runtime: {} };
	`
	
	wsHookScript := c.getWebSocketHookScript()
	
	// Use Page.addScriptToEvaluateOnNewDocument via CDP
	if err := chromedp.Run(ctx,
		chromedp.ActionFunc(func(ctx context.Context) error {
			_, err := page.AddScriptToEvaluateOnNewDocument(antiDetectionScript).Do(ctx)
			if err != nil {
				return fmt.Errorf("failed to add anti-detection script: %w", err)
			}
			
			_, err = page.AddScriptToEvaluateOnNewDocument(wsHookScript).Do(ctx)
			if err != nil {
				return fmt.Errorf("failed to add WebSocket hook script: %w", err)
			}
			
			return nil
		}),
	); err != nil {
		return fmt.Errorf("failed to inject scripts: %w", err)
	}

	// Now navigate to the room - scripts will run before page JS
	c.log.Infof("Navigating to Kosmi room: %s", c.roomURL)
	if err := chromedp.Run(ctx,
		chromedp.Navigate(c.roomURL),
		chromedp.WaitReady("body"),
	); err != nil {
		return fmt.Errorf("failed to navigate to room: %w", err)
	}

	// Wait for page to load and WebSocket to connect
	c.log.Info("Waiting for page to load and WebSocket to connect...")
	time.Sleep(5 * time.Second)
	
	// Check if WebSocket is connected
	var wsStatus map[string]interface{}
	checkScript := `
		(function() {
			return {
				hookInstalled: !!window.__KOSMI_WS_HOOK_INSTALLED__,
				wsFound: !!window.__KOSMI_WS__,
				wsConnected: window.__KOSMI_WS__ ? window.__KOSMI_WS__.readyState === WebSocket.OPEN : false,
				wsState: window.__KOSMI_WS__ ? window.__KOSMI_WS__.readyState : -1
			};
		})();
	`
	if err := chromedp.Run(ctx, chromedp.Evaluate(checkScript, &wsStatus)); err == nil {
		c.log.Infof("WebSocket status: %+v", wsStatus)
	}

	// Get cookies from the browser
	c.log.Info("Extracting cookies from browser session...")
	cookies, err := c.getCookies()
	if err != nil {
		return fmt.Errorf("failed to get cookies: %w", err)
	}

	c.log.Infof("Obtained %d cookies from browser", len(cookies))

	// Set up HTTP client with cookies
	jar, err := cookiejar.New(nil)
	if err != nil {
		return fmt.Errorf("failed to create cookie jar: %w", err)
	}

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

	// Add cookies to the jar
	u, _ := url.Parse("https://engine.kosmi.io")
	c.httpClient.Jar.SetCookies(u, cookies)

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

	c.log.Info("Successfully connected - browser session established with cookies")

	// Start message listener (using WebSocket hook in browser)
	go c.listenForMessages()

	return nil
}

// getCookies extracts cookies from the Chrome session
func (c *HybridClient) getCookies() ([]*http.Cookie, error) {
	var cookiesData []map[string]interface{}
	
	script := `
		(function() {
			return document.cookie.split(';').map(c => {
				const parts = c.trim().split('=');
				return {
					name: parts[0],
					value: parts.slice(1).join('=')
				};
			});
		})();
	`
	
	if err := chromedp.Run(c.ctx, chromedp.Evaluate(script, &cookiesData)); err != nil {
		return nil, err
	}

	cookies := make([]*http.Cookie, 0, len(cookiesData))
	for _, cd := range cookiesData {
		if name, ok := cd["name"].(string); ok {
			if value, ok := cd["value"].(string); ok {
				cookies = append(cookies, &http.Cookie{
					Name:  name,
					Value: value,
				})
			}
		}
	}

	return cookies, nil
}

// injectAntiDetection injects anti-detection scripts
func (c *HybridClient) injectAntiDetection() error {
	script := `
		Object.defineProperty(navigator, 'webdriver', { get: () => false });
		Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
		Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
		window.chrome = { runtime: {} };
	`
	
	return chromedp.Run(c.ctx, chromedp.ActionFunc(func(ctx context.Context) error {
		return chromedp.Evaluate(script, nil).Do(ctx)
	}))
}

// injectWebSocketHook injects the WebSocket interception script
func (c *HybridClient) injectWebSocketHook() error {
	script := c.getWebSocketHookScript()
	
	return chromedp.Run(c.ctx, chromedp.ActionFunc(func(ctx context.Context) error {
		return chromedp.Evaluate(script, nil).Do(ctx)
	}))
}

// getWebSocketHookScript returns the WebSocket hook JavaScript
func (c *HybridClient) getWebSocketHookScript() string {
	return `
		(function() {
			if (window.__KOSMI_WS_HOOK_INSTALLED__) return;
			
			const OriginalWebSocket = window.WebSocket;
			window.__KOSMI_MESSAGE_QUEUE__ = [];
			window.__KOSMI_WS__ = null;  // Store reference to the WebSocket
			
			window.WebSocket = function(url, protocols) {
				const socket = new OriginalWebSocket(url, protocols);
				
				if (url.includes('engine.kosmi.io') || url.includes('gql-ws')) {
					window.__KOSMI_WS_CONNECTED__ = true;
					window.__KOSMI_WS__ = socket;  // Store the WebSocket reference
					
					const originalAddEventListener = socket.addEventListener.bind(socket);
					socket.addEventListener = function(type, listener, options) {
						if (type === 'message') {
							const wrappedListener = function(event) {
								try {
									const data = JSON.parse(event.data);
									window.__KOSMI_MESSAGE_QUEUE__.push({
										timestamp: Date.now(),
										data: data
									});
								} catch (e) {}
								return listener.call(this, event);
							};
							return originalAddEventListener(type, wrappedListener, options);
						}
						return originalAddEventListener(type, listener, options);
					};
					
					let realOnMessage = null;
					Object.defineProperty(socket, 'onmessage', {
						get: function() { return realOnMessage; },
						set: function(handler) {
							realOnMessage = function(event) {
								try {
									const data = JSON.parse(event.data);
									window.__KOSMI_MESSAGE_QUEUE__.push({
										timestamp: Date.now(),
										data: data
									});
								} catch (e) {}
								if (handler) { handler.call(socket, event); }
							};
						},
						configurable: true
					});
				}
				
				return socket;
			};
			
			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_WS_HOOK_INSTALLED__ = true;
		})();
	`
}

// listenForMessages polls for messages from the WebSocket queue
func (c *HybridClient) listenForMessages() {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

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

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

		script := `
			(function() {
				if (!window.__KOSMI_MESSAGE_QUEUE__) return [];
				const messages = window.__KOSMI_MESSAGE_QUEUE__.slice();
				window.__KOSMI_MESSAGE_QUEUE__ = [];
				return messages;
			})();
		`

		if err := chromedp.Run(c.ctx, chromedp.Evaluate(script, &messages)); err != nil {
			<-ticker.C
			continue
		}

		if len(messages) > 0 {
			c.log.Infof("Processing %d messages from queue", len(messages))
		}

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

		<-ticker.C
	}
}

// processMessage processes a WebSocket message
func (c *HybridClient) 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
	}

	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 via WebSocket using browser automation
func (c *HybridClient) SendMessage(text string) error {
	c.log.Infof("SendMessage called with text: %s", text)
	
	c.mu.RLock()
	if !c.connected {
		c.mu.RUnlock()
		c.log.Error("SendMessage: not connected")
		return fmt.Errorf("not connected")
	}
	ctx := c.ctx
	c.mu.RUnlock()

	c.log.Infof("Sending message to room %s via WebSocket", c.roomID)

	// Escape the text for JavaScript
	escapedText := strings.ReplaceAll(text, `\`, `\\`)
	escapedText = strings.ReplaceAll(escapedText, `"`, `\"`)
	escapedText = strings.ReplaceAll(escapedText, "\n", `\n`)
	
	// JavaScript to send message via WebSocket
	script := fmt.Sprintf(`
		(function() {
			// Find the Kosmi WebSocket
			if (!window.__KOSMI_WS__) {
				return { success: false, error: "WebSocket not found" };
			}
			
			const ws = window.__KOSMI_WS__;
			
			if (ws.readyState !== WebSocket.OPEN) {
				return { success: false, error: "WebSocket not open, state: " + ws.readyState };
			}
			
			// GraphQL-WS message format
			const message = {
				id: "send-" + Date.now(),
				type: "start",
				payload: {
					query: "mutation SendMessage($body: String!, $roomID: ID!) { sendMessage(body: $body, roomID: $roomID) { id body time user { id username displayName } } }",
					variables: {
						body: "%s",
						roomID: "%s"
					}
				}
			};
			
			try {
				ws.send(JSON.stringify(message));
				return { success: true, message: "Sent via WebSocket" };
			} catch (e) {
				return { success: false, error: e.toString() };
			}
		})();
	`, escapedText, c.roomID)

	var result map[string]interface{}
	err := chromedp.Run(ctx,
		chromedp.Evaluate(script, &result),
	)
	
	if err != nil {
		c.log.Errorf("Failed to execute send script: %v", err)
		return fmt.Errorf("failed to execute send script: %w", err)
	}

	c.log.Debugf("Send result: %+v", result)

	if success, ok := result["success"].(bool); !ok || !success {
		errorMsg := "unknown error"
		if errStr, ok := result["error"].(string); ok {
			errorMsg = errStr
		}
		c.log.Errorf("Failed to send message: %s", errorMsg)
		return fmt.Errorf("failed to send message: %s", errorMsg)
	}

	c.log.Infof("✅ Successfully sent message via WebSocket: %s", text)
	return nil
}

// OnMessage sets the callback for new messages
func (c *HybridClient) OnMessage(callback func(*NewMessagePayload)) {
	c.messageCallback = callback
}

// Disconnect closes the browser and cleans up
func (c *HybridClient) Disconnect() error {
	c.mu.Lock()
	c.connected = false
	c.mu.Unlock()

	c.log.Info("Closing hybrid client")

	if c.cancel != nil {
		c.cancel()
	}

	return nil
}

