package domain

import (
	"sync"
	"time"
)

// NotificationChannel represents notification channels for offline parties
type NotificationChannel struct {
	Email     string
	Phone     string
	PushToken string
}

// HasAnyChannel returns true if any notification channel is configured
func (nc *NotificationChannel) HasAnyChannel() bool {
	return nc != nil && (nc.Email != "" || nc.Phone != "" || nc.PushToken != "")
}

// RegisteredParty represents a party registered with the router
type RegisteredParty struct {
	PartyID      string
	Role         string // persistent, delegate, temporary
	Version      string
	RegisteredAt time.Time
	LastSeen     time.Time
	Online       bool                 // Whether the party is currently connected
	Notification *NotificationChannel // Optional notification channels for offline mode
}

// IsOfflineMode returns true if the party operates in offline mode (has notification channels)
func (p *RegisteredParty) IsOfflineMode() bool {
	return p.Notification != nil && p.Notification.HasAnyChannel()
}

// PartyRegistry manages registered parties
type PartyRegistry struct {
	parties map[string]*RegisteredParty
	mu      sync.RWMutex
}

// NewPartyRegistry creates a new party registry
func NewPartyRegistry() *PartyRegistry {
	return &PartyRegistry{
		parties: make(map[string]*RegisteredParty),
	}
}

// Register registers a party
func (r *PartyRegistry) Register(partyID, role, version string) *RegisteredParty {
	return r.RegisterWithNotification(partyID, role, version, nil)
}

// RegisterWithNotification registers a party with optional notification channels
func (r *PartyRegistry) RegisterWithNotification(partyID, role, version string, notification *NotificationChannel) *RegisteredParty {
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now()
	party := &RegisteredParty{
		PartyID:      partyID,
		Role:         role,
		Version:      version,
		RegisteredAt: now,
		LastSeen:     now,
		Online:       true,
		Notification: notification,
	}

	r.parties[partyID] = party
	return party
}

// Get retrieves a registered party
func (r *PartyRegistry) Get(partyID string) (*RegisteredParty, bool) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	party, exists := r.parties[partyID]
	return party, exists
}

// GetAll returns all registered parties
func (r *PartyRegistry) GetAll() []*RegisteredParty {
	r.mu.RLock()
	defer r.mu.RUnlock()

	parties := make([]*RegisteredParty, 0, len(r.parties))
	for _, party := range r.parties {
		parties = append(parties, party)
	}
	return parties
}

// GetByRole returns registered parties filtered by role
func (r *PartyRegistry) GetByRole(role string) []*RegisteredParty {
	r.mu.RLock()
	defer r.mu.RUnlock()

	parties := make([]*RegisteredParty, 0)
	for _, party := range r.parties {
		if party.Role == role {
			parties = append(parties, party)
		}
	}
	return parties
}

// UpdateLastSeen updates the last seen timestamp
func (r *PartyRegistry) UpdateLastSeen(partyID string) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if party, exists := r.parties[partyID]; exists {
		party.LastSeen = time.Now()
	}
}

// Unregister removes a party from the registry
func (r *PartyRegistry) Unregister(partyID string) {
	r.mu.Lock()
	defer r.mu.Unlock()

	delete(r.parties, partyID)
}

// Count returns the number of registered parties
func (r *PartyRegistry) Count() int {
	r.mu.RLock()
	defer r.mu.RUnlock()

	return len(r.parties)
}

// SetOnline sets the online status of a party
func (r *PartyRegistry) SetOnline(partyID string, online bool) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if party, exists := r.parties[partyID]; exists {
		party.Online = online
		if online {
			party.LastSeen = time.Now()
		}
	}
}

// IsOnline checks if a party is currently online
func (r *PartyRegistry) IsOnline(partyID string) bool {
	r.mu.RLock()
	defer r.mu.RUnlock()

	if party, exists := r.parties[partyID]; exists {
		return party.Online
	}
	return false
}

// GetOnlineParties returns all online parties
func (r *PartyRegistry) GetOnlineParties() []*RegisteredParty {
	r.mu.RLock()
	defer r.mu.RUnlock()

	parties := make([]*RegisteredParty, 0)
	for _, party := range r.parties {
		if party.Online {
			parties = append(parties, party)
		}
	}
	return parties
}

// GetOfflineParties returns all parties that are offline (have notification channels but not connected)
func (r *PartyRegistry) GetOfflineParties() []*RegisteredParty {
	r.mu.RLock()
	defer r.mu.RUnlock()

	parties := make([]*RegisteredParty, 0)
	for _, party := range r.parties {
		if !party.Online && party.IsOfflineMode() {
			parties = append(parties, party)
		}
	}
	return parties
}

// MarkStalePartiesOffline marks parties as offline if they haven't sent a heartbeat within the timeout
// Returns the list of parties that were marked offline
func (r *PartyRegistry) MarkStalePartiesOffline(timeout time.Duration) []*RegisteredParty {
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now()
	staleParties := make([]*RegisteredParty, 0)

	for _, party := range r.parties {
		if party.Online && now.Sub(party.LastSeen) > timeout {
			party.Online = false
			staleParties = append(staleParties, party)
		}
	}

	return staleParties
}

// Heartbeat updates the last seen timestamp and marks the party as online
func (r *PartyRegistry) Heartbeat(partyID string) bool {
	r.mu.Lock()
	defer r.mu.Unlock()

	if party, exists := r.parties[partyID]; exists {
		party.LastSeen = time.Now()
		party.Online = true
		return true
	}
	return false
}
