package whatsapp

import (
	"context"
	"fmt"
	"sync"

	"git.warky.dev/wdevs/whatshooked/pkg/config"
	"git.warky.dev/wdevs/whatshooked/pkg/events"
	"git.warky.dev/wdevs/whatshooked/pkg/logging"
	"git.warky.dev/wdevs/whatshooked/pkg/whatsapp/businessapi"
	"git.warky.dev/wdevs/whatshooked/pkg/whatsapp/whatsmeow"

	"go.mau.fi/whatsmeow/types"
)

// Manager manages multiple WhatsApp client connections
type Manager struct {
	clients        map[string]Client
	mu             sync.RWMutex
	eventBus       *events.EventBus
	mediaConfig    config.MediaConfig
	config         *config.Config
	configPath     string
	onConfigUpdate func(*config.Config) error
}

// NewManager creates a new WhatsApp manager
func NewManager(eventBus *events.EventBus, mediaConfig config.MediaConfig, cfg *config.Config, configPath string, onConfigUpdate func(*config.Config) error) *Manager {
	return &Manager{
		clients:        make(map[string]Client),
		eventBus:       eventBus,
		mediaConfig:    mediaConfig,
		config:         cfg,
		configPath:     configPath,
		onConfigUpdate: onConfigUpdate,
	}
}

// Connect establishes a connection to a WhatsApp account using the appropriate client type
func (m *Manager) Connect(ctx context.Context, cfg config.WhatsAppConfig) error {
	m.mu.Lock()
	if _, exists := m.clients[cfg.ID]; exists {
		m.mu.Unlock()
		return fmt.Errorf("client %s already connected", cfg.ID)
	}

	var client Client
	var err error

	// Factory pattern based on type
	switch cfg.Type {
	case "business-api":
		onWABAResolved := func(accountID, wabaID string) {
			m.updateWABAInConfig(accountID, wabaID)
		}
		client, err = businessapi.NewClient(cfg, m.eventBus, m.mediaConfig, onWABAResolved)
	case "whatsmeow", "":
		// Create callback for phone number updates
		onPhoneUpdate := func(accountID, phoneNumber string) {
			m.updatePhoneNumberInConfig(accountID, phoneNumber)
		}
		client, err = whatsmeow.NewClient(cfg, m.eventBus, m.mediaConfig, onPhoneUpdate)
	default:
		m.mu.Unlock()
		return fmt.Errorf("unknown client type: %s", cfg.Type)
	}

	if err != nil {
		m.mu.Unlock()
		return fmt.Errorf("failed to create client: %w", err)
	}

	// Register client immediately so it's available for QR code serving during pairing
	m.clients[cfg.ID] = client
	m.mu.Unlock()

	// Connect in background (this can block during QR pairing)
	go func() {
		if err := client.Connect(ctx); err != nil {
			logging.Error("Failed to connect client", "account_id", cfg.ID, "error", err)
			// Remove client if connection fails
			m.mu.Lock()
			delete(m.clients, cfg.ID)
			m.mu.Unlock()
			m.eventBus.Publish(events.WhatsAppPairFailedEvent(ctx, cfg.ID, err))
		} else {
			logging.Info("Client connected", "account_id", cfg.ID, "type", client.GetType())
		}
	}()

	return nil
}

// Disconnect disconnects a WhatsApp client
func (m *Manager) Disconnect(id string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	client, exists := m.clients[id]
	if !exists {
		return fmt.Errorf("client %s not found", id)
	}

	if err := client.Disconnect(); err != nil {
		return fmt.Errorf("failed to disconnect: %w", err)
	}

	delete(m.clients, id)
	logging.Info("Client disconnected", "account_id", id)
	return nil
}

// DisconnectAll disconnects all WhatsApp clients
func (m *Manager) DisconnectAll() {
	m.mu.Lock()
	defer m.mu.Unlock()

	for id, client := range m.clients {
		if err := client.Disconnect(); err != nil {
			logging.Error("Failed to disconnect client", "account_id", id, "error", err)
		} else {
			logging.Info("Client disconnected", "account_id", id)
		}
	}
	m.clients = make(map[string]Client)
}

// SendTextMessage sends a text message from a specific account
func (m *Manager) SendTextMessage(ctx context.Context, accountID string, jid types.JID, text string) error {
	m.mu.RLock()
	client, exists := m.clients[accountID]
	m.mu.RUnlock()

	if !exists {
		return fmt.Errorf("client %s not found", accountID)
	}

	_, err := client.SendTextMessage(ctx, jid, text)
	return err
}

// SendImage sends an image message from a specific account
func (m *Manager) SendImage(ctx context.Context, accountID string, jid types.JID, imageData []byte, mimeType string, caption string) error {
	m.mu.RLock()
	client, exists := m.clients[accountID]
	m.mu.RUnlock()

	if !exists {
		return fmt.Errorf("client %s not found", accountID)
	}

	_, err := client.SendImage(ctx, jid, imageData, mimeType, caption)
	return err
}

// SendVideo sends a video message from a specific account
func (m *Manager) SendVideo(ctx context.Context, accountID string, jid types.JID, videoData []byte, mimeType string, caption string) error {
	m.mu.RLock()
	client, exists := m.clients[accountID]
	m.mu.RUnlock()

	if !exists {
		return fmt.Errorf("client %s not found", accountID)
	}

	_, err := client.SendVideo(ctx, jid, videoData, mimeType, caption)
	return err
}

// SendDocument sends a document message from a specific account
func (m *Manager) SendDocument(ctx context.Context, accountID string, jid types.JID, documentData []byte, mimeType string, filename string, caption string) error {
	m.mu.RLock()
	client, exists := m.clients[accountID]
	m.mu.RUnlock()

	if !exists {
		return fmt.Errorf("client %s not found", accountID)
	}

	_, err := client.SendDocument(ctx, jid, documentData, mimeType, filename, caption)
	return err
}

// GetClient returns a client by ID
func (m *Manager) GetClient(id string) (Client, bool) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	client, exists := m.clients[id]
	return client, exists
}

// updateWABAInConfig stores the resolved WABA ID back into the in-memory config and syncs to DB/file
func (m *Manager) updateWABAInConfig(accountID, wabaID string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	for i := range m.config.WhatsApp {
		if m.config.WhatsApp[i].ID == accountID {
			if m.config.WhatsApp[i].BusinessAPI == nil {
				return
			}
			if m.config.WhatsApp[i].BusinessAPI.WABAId == wabaID {
				return // already up to date
			}
			m.config.WhatsApp[i].BusinessAPI.WABAId = wabaID
			logging.Info("Updated WABA ID in config", "account_id", accountID, "waba_id", wabaID)

			if m.onConfigUpdate != nil {
				if err := m.onConfigUpdate(m.config); err != nil {
					logging.Error("Failed to save config after WABA ID update", "account_id", accountID, "error", err)
				}
			}
			break
		}
	}
}

// updatePhoneNumberInConfig updates the phone number for an account in config and saves it
func (m *Manager) updatePhoneNumberInConfig(accountID, phoneNumber string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Find and update the account in config
	for i := range m.config.WhatsApp {
		if m.config.WhatsApp[i].ID == accountID {
			if m.config.WhatsApp[i].PhoneNumber != phoneNumber {
				m.config.WhatsApp[i].PhoneNumber = phoneNumber
				logging.Info("Updated phone number in config", "account_id", accountID, "phone", phoneNumber)

				// Save config if callback is available
				if m.onConfigUpdate != nil {
					if err := m.onConfigUpdate(m.config); err != nil {
						logging.Error("Failed to save config after phone update", "account_id", accountID, "error", err)
					} else {
						logging.Debug("Config saved with updated phone number", "account_id", accountID)
					}
				}
			}
			break
		}
	}
}
