package email

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"sync"
)

// Manager manages the lifecycle of all email receiving connections.
type Manager struct {
	logger  *slog.Logger
	service *Service
	trigger *Trigger
	outbox  *OutboxService

	mu      sync.Mutex
	conns   map[string]Stopper // provider_id -> stopper
	stopped bool
}

func NewManager(log *slog.Logger, service *Service, trigger *Trigger, outbox *OutboxService) *Manager {
	return &Manager{
		logger:  log.With(slog.String("component", "email_manager")),
		service: service,
		trigger: trigger,
		outbox:  outbox,
		conns:   make(map[string]Stopper),
	}
}

// Start initializes receiving for all providers that have readable bindings.
func (m *Manager) Start(ctx context.Context) error {
	providers, err := m.service.ListProviders(ctx, "")
	if err != nil {
		return fmt.Errorf("list email providers: %w", err)
	}

	for _, p := range providers {
		bindings, err := m.service.ListReadableBindingsByProvider(ctx, p.ID)
		if err != nil {
			m.logger.Error("failed to list bindings", slog.String("provider", p.ID), slog.Any("error", err))
			continue
		}
		if len(bindings) == 0 {
			continue
		}
		if err := m.startProvider(ctx, p); err != nil {
			m.logger.Error("failed to start provider", slog.String("provider", p.ID), slog.Any("error", err))
		}
	}
	return nil
}

func (m *Manager) startProvider(ctx context.Context, p ProviderResponse) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.stopped {
		return errors.New("manager is stopped")
	}
	if _, exists := m.conns[p.ID]; exists {
		return nil
	}

	receiver, err := m.service.registry.GetReceiver(ProviderName(p.Provider))
	if err != nil {
		return err
	}

	config := p.Config
	if config == nil {
		config = make(map[string]any)
	}
	config["_provider_id"] = p.ID

	stopper, err := receiver.StartReceiving(ctx, config, m.trigger.HandleInbound)
	if err != nil {
		return fmt.Errorf("start receiving for %s: %w", p.ID, err)
	}

	m.conns[p.ID] = stopper
	m.logger.Info("started email receiving", slog.String("provider_id", p.ID), slog.String("type", p.Provider))
	return nil
}

// RefreshProvider restarts receiving for a specific provider.
func (m *Manager) RefreshProvider(ctx context.Context, providerID string) error {
	m.stopProvider(ctx, providerID)

	p, err := m.service.GetProvider(ctx, providerID)
	if err != nil {
		return err
	}

	bindings, err := m.service.ListReadableBindingsByProvider(ctx, providerID)
	if err != nil {
		return err
	}
	if len(bindings) == 0 {
		return nil
	}

	return m.startProvider(ctx, p)
}

func (m *Manager) stopProvider(ctx context.Context, providerID string) {
	m.mu.Lock()
	stopper, exists := m.conns[providerID]
	if exists {
		delete(m.conns, providerID)
	}
	m.mu.Unlock()

	if exists && stopper != nil {
		if err := stopper.Stop(ctx); err != nil {
			m.logger.Error("failed to stop provider", slog.String("provider_id", providerID), slog.Any("error", err))
		}
	}
}

// Stop gracefully shuts down all receiving connections.
func (m *Manager) Stop(ctx context.Context) {
	m.mu.Lock()
	m.stopped = true
	conns := make(map[string]Stopper, len(m.conns))
	for k, v := range m.conns {
		conns[k] = v
	}
	m.conns = make(map[string]Stopper)
	m.mu.Unlock()

	for id, stopper := range conns {
		if err := stopper.Stop(ctx); err != nil {
			m.logger.Error("failed to stop provider", slog.String("provider_id", id), slog.Any("error", err))
		}
	}
}

// SendEmail sends an email through the specified provider, recording to outbox.
// If providerID is empty, it falls back to the first writable binding for the bot.
func (m *Manager) SendEmail(ctx context.Context, botID string, providerID string, msg OutboundEmail) (string, error) {
	if providerID == "" {
		binding, err := m.service.GetBotBinding(ctx, botID)
		if err != nil {
			return "", err
		}
		if !binding.CanWrite {
			return "", fmt.Errorf("email write permission denied for bot %s", botID)
		}
		providerID = binding.EmailProviderID
	}

	providerName, config, err := m.service.ProviderConfig(ctx, providerID)
	if err != nil {
		return "", err
	}
	if config == nil {
		config = make(map[string]any)
	}
	config["_provider_id"] = providerID

	sender, err := m.service.registry.GetSender(providerName)
	if err != nil {
		return "", err
	}

	fromAddr, _ := config["username"].(string)

	outboxID, err := m.outbox.Create(ctx, providerID, botID, msg, fromAddr)
	if err != nil {
		return "", fmt.Errorf("record outbox: %w", err)
	}

	messageID, err := sender.Send(ctx, config, msg)
	if err != nil {
		_ = m.outbox.MarkFailed(ctx, outboxID, err.Error())
		return "", fmt.Errorf("send email: %w", err)
	}

	_ = m.outbox.MarkSent(ctx, outboxID, messageID)
	return messageID, nil
}
