package bkosmi

import (
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/jackbox"
)

const (
	defaultWebSocketURL = "wss://engine.kosmi.io/gql-ws"
)

// KosmiClient interface for different client implementations
type KosmiClient interface {
	Connect() error
	Disconnect() error
	SendMessage(text string) error
	OnMessage(callback func(*NewMessagePayload))
	IsConnected() bool
}

// Bkosmi represents the Kosmi bridge
type Bkosmi struct {
	*bridge.Config
	client        KosmiClient
	roomID        string
	roomURL       string
	connected     bool
	authDone      bool // Signals that authentication is complete (like IRC bridge)
	msgChannel    chan config.Message
	jackboxClient *jackbox.Client
}

// New creates a new Kosmi bridge instance
func New(cfg *bridge.Config) bridge.Bridger {
	b := &Bkosmi{
		Config:     cfg,
		msgChannel: make(chan config.Message, 100),
	}

	return b
}

// Connect establishes connection to the Kosmi room
func (b *Bkosmi) Connect() error {
	b.Log.Info("Connecting to Kosmi")

	// Get room URL from config
	b.roomURL = b.GetString("RoomURL")
	if b.roomURL == "" {
		return fmt.Errorf("RoomURL is required in configuration")
	}

	// Extract room ID from URL
	roomID, err := extractRoomID(b.roomURL)
	if err != nil {
		return fmt.Errorf("failed to extract room ID from URL %s: %w", b.roomURL, err)
	}
	b.roomID = roomID
	b.Log.Infof("Extracted room ID: %s", b.roomID)

	// Check if we need authentication
	email := b.GetString("Email")
	password := b.GetString("Password")
	
	var token string
	if email != "" && password != "" {
		// Try to load cached token first
		cachedToken, err := loadTokenCache(email, b.Log)
		if err != nil {
			b.Log.Warnf("Failed to load token cache: %v", err)
		}
		
		if cachedToken != nil {
			// Use cached token
			token = cachedToken.Token
		} else {
			// No valid cache, authenticate with browser
			b.Log.Info("Authenticating with email/password...")
			token, err = loginWithChromedp(email, password, b.Log)
			if err != nil {
				return fmt.Errorf("authentication failed: %w", err)
			}
			b.Log.Info("✅ Authentication successful")
			
			// Save token to cache
			if err := saveTokenCache(token, email, b.Log); err != nil {
				b.Log.Warnf("Failed to cache token: %v", err)
			}
		}
	} else {
		b.Log.Info("No credentials provided, using anonymous access")
		// token will be empty, client will get anonymous token
	}

	// Create GraphQL WebSocket client with token
	b.client = NewGraphQLWSClient(b.roomURL, b.roomID, token, b.Log)

	// Register message handler
	b.client.OnMessage(b.handleIncomingMessage)

	// Connect to Kosmi
	if err := b.client.Connect(); err != nil {
		return fmt.Errorf("failed to connect to Kosmi: %w", err)
	}

	b.connected = true
	b.authDone = true // Signal that authentication is complete
	b.Log.Info("Successfully connected to Kosmi")

	return nil
}

// Disconnect closes the connection to Kosmi
func (b *Bkosmi) Disconnect() error {
	b.Log.Info("Disconnecting from Kosmi")

	if b.client != nil {
		if err := b.client.Disconnect(); err != nil {
			b.Log.Errorf("Error closing Kosmi client: %v", err)
		}
	}

	close(b.msgChannel)
	b.connected = false

	return nil
}

// JoinChannel joins a Kosmi room (no-op as we connect to a specific room)
func (b *Bkosmi) JoinChannel(channel config.ChannelInfo) error {
	// Wait for authentication to complete before proceeding
	// This ensures the WebSocket connection is fully established (like IRC bridge)
	for {
		if b.authDone {
			break
		}
		time.Sleep(time.Second)
	}
	
	// Kosmi doesn't have a concept of joining channels after connection
	// The room is specified in the configuration and joined on Connect()
	b.Log.Debugf("Channel ready: %s (connected via room URL)", channel.Name)
	return nil
}

// Send sends a message to Kosmi
func (b *Bkosmi) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Sending message to Kosmi: %#v", msg)

	// Ignore delete messages
	if msg.Event == config.EventMsgDelete {
		return "", nil
	}

	// Check if we're connected
	if !b.connected || b.client == nil {
		b.Log.Error("Not connected to Kosmi, dropping message")
		return "", nil
	}

	// The gateway already formatted the username with RemoteNickFormat
	// So msg.Username contains the formatted string like "[irc] <cottongin>"
	// Just send: username + text
	formattedMsg := fmt.Sprintf("%s%s", msg.Username, msg.Text)

	// Send message to Kosmi
	if err := b.client.SendMessage(formattedMsg); err != nil {
		b.Log.Errorf("Failed to send message to Kosmi: %v", err)
		return "", err
	}

	return "", nil
}

// handleIncomingMessage processes messages received from Kosmi
func (b *Bkosmi) handleIncomingMessage(payload *NewMessagePayload) {
	// Extract message details
	body := payload.Data.NewMessage.Body
	username := payload.Data.NewMessage.User.DisplayName
	if username == "" {
		username = payload.Data.NewMessage.User.Username
	}
	if username == "" {
		username = "Unknown"
	}

	timestamp := time.Unix(payload.Data.NewMessage.Time, 0)

	b.Log.Infof("Received message from Kosmi: [%s] %s: %s", timestamp.Format(time.RFC3339), username, body)

	// Check if this is our own message (to avoid echo)
	// Messages we send have [irc] prefix (from RemoteNickFormat)
	if strings.HasPrefix(body, "[irc]") {
		b.Log.Debug("Ignoring our own echoed message (has [irc] prefix)")
		return
	}

	// Check for votes (thisgame++ or thisgame--)
	// Only process votes from non-relayed messages
	if !jackbox.IsRelayedMessage(body) {
		if isVote, voteType := jackbox.DetectVote(body); isVote {
			b.Log.Debugf("Detected vote from %s: %s", username, voteType)
			if b.jackboxClient != nil {
				go func() {
					if err := b.jackboxClient.SendVote(username, voteType, timestamp); err != nil {
						b.Log.Errorf("Failed to send vote to Jackbox API: %v", err)
					}
				}()
			}
		}
	}

	// Create Matterbridge message
	// Use "main" as the channel name for gateway matching
	// Don't add prefix here - let the gateway's RemoteNickFormat handle it
	rmsg := config.Message{
		Username:  username,
		Text:      body,
		Channel:   "main",
		Account:   b.Account,
		UserID:    username,
		Timestamp: timestamp,
		Protocol:  "kosmi",
	}

	// Send to Matterbridge
	b.Log.Debugf("Forwarding to Matterbridge channel=%s account=%s: %s", rmsg.Channel, rmsg.Account, rmsg.Text)

	if b.Remote == nil {
		b.Log.Error("Remote channel is nil! Cannot forward message")
		return
	}

	b.Remote <- rmsg
}

// extractRoomID extracts the room ID from a Kosmi room URL
// Supports formats:
//   - https://app.kosmi.io/room/@roomname
//   - https://app.kosmi.io/room/roomid
func extractRoomID(url string) (string, error) {
	// Remove trailing slash if present
	url = strings.TrimSuffix(url, "/")

	// Pattern to match Kosmi room URLs
	patterns := []string{
		`https?://app\.kosmi\.io/room/(@?[a-zA-Z0-9_-]+)`,
		`app\.kosmi\.io/room/(@?[a-zA-Z0-9_-]+)`,
		`/room/(@?[a-zA-Z0-9_-]+)`,
	}

	for _, pattern := range patterns {
		re := regexp.MustCompile(pattern)
		matches := re.FindStringSubmatch(url)
		if len(matches) >= 2 {
			roomID := matches[1]
			// Ensure @ prefix is present (required for WebSocket API)
			if !strings.HasPrefix(roomID, "@") {
				roomID = "@" + roomID
			}
			return roomID, nil
		}
	}

	// If no pattern matches, assume the entire string is the room ID
	// This allows for simple room ID configuration
	parts := strings.Split(url, "/")
	if len(parts) > 0 {
		lastPart := parts[len(parts)-1]
		if lastPart != "" {
			return strings.TrimPrefix(lastPart, "@"), nil
		}
	}

	return "", fmt.Errorf("could not extract room ID from URL: %s", url)
}

// SetJackboxClient sets the Jackbox API client for this bridge
func (b *Bkosmi) SetJackboxClient(client *jackbox.Client) {
	b.jackboxClient = client
	b.Log.Info("Jackbox client injected into Kosmi bridge")
}
