package channel

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
)

var (
	ErrChannelNotFound      = errors.New("channel not found")
	ErrChannelAlreadyExists = errors.New("channel already exists")
	ErrChannelFull          = errors.New("channel is full")
	ErrInvalidChannelName   = errors.New("invalid channel name")
	ErrUserNotInChannel     = errors.New("user not in channel")
)

// Manager manages all channels
type Manager struct {
	channels map[string]*Channel
	names    map[string]bool // for duplicate checking
	mu       sync.RWMutex
}

// NewManager creates a new channel manager
func NewManager() *Manager {
	return &Manager{
		channels: make(map[string]*Channel),
		names:    make(map[string]bool),
	}
}

// CreateChannel creates a new channel
func (m *Manager) CreateChannel(name string, ownerID string) (*Channel, error) {
	if name == "" || len(name) < 2 || len(name) > 50 {
		return nil, ErrInvalidChannelName
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	if m.names[name] {
		return nil, ErrChannelAlreadyExists
	}

	id := uuid.New().String()
	channel := NewChannel(id, name, ownerID)
	channel.AddMember(ownerID) // Owner is automatically a member

	m.channels[id] = channel
	m.names[name] = true

	return channel, nil
}

// GetChannel retrieves a channel by ID
func (m *Manager) GetChannel(channelID string) (*Channel, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	channel, exists := m.channels[channelID]
	if !exists {
		return nil, ErrChannelNotFound
	}

	return channel, nil
}

// ListChannels returns all active channels
func (m *Manager) ListChannels() []*Channel {
	m.mu.RLock()
	defer m.mu.RUnlock()

	channels := make([]*Channel, 0, len(m.channels))
	for _, ch := range m.channels {
		if ch.Status == StatusActive {
			channels = append(channels, ch)
		}
	}
	return channels
}

// DeleteChannel deletes a channel (soft or hard delete)
func (m *Manager) DeleteChannel(channelID string, hardDelete bool) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	channel, exists := m.channels[channelID]
	if !exists {
		return ErrChannelNotFound
	}

	if hardDelete {
		delete(m.channels, channelID)
		delete(m.names, channel.Name)
	} else {
		// Soft delete (archive)
		channel.Status = StatusArchived
		channel.UpdatedAt = time.Now()
	}

	return nil
}

// JoinChannel adds a user to a channel
func (m *Manager) JoinChannel(channelID string, userID string) (*Channel, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	channel, exists := m.channels[channelID]
	if !exists {
		return nil, ErrChannelNotFound
	}

	if channel.Status != StatusActive {
		return nil, fmt.Errorf("cannot join inactive channel")
	}

	if channel.IsFull() {
		return nil, ErrChannelFull
	}

	channel.AddMember(userID)
	return channel, nil
}

// LeaveChannel removes a user from a channel
func (m *Manager) LeaveChannel(channelID string, userID string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	channel, exists := m.channels[channelID]
	if !exists {
		return ErrChannelNotFound
	}

	if !channel.IsMember(userID) {
		return ErrUserNotInChannel
	}

	channel.RemoveMember(userID)
	return nil
}

// GetChannelMembers returns members of a channel
func (m *Manager) GetChannelMembers(channelID string) ([]string, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	channel, exists := m.channels[channelID]
	if !exists {
		return nil, ErrChannelNotFound
	}

	return channel.GetMembers(), nil
}

// UpdateChannel updates channel properties
func (m *Manager) UpdateChannel(channelID string, name string, description string, isPublic bool, maxUsers int32) (*Channel, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	channel, exists := m.channels[channelID]
	if !exists {
		return nil, ErrChannelNotFound
	}

	// Check name uniqueness if changing name
	if name != "" && name != channel.Name {
		if m.names[name] {
			return nil, ErrChannelAlreadyExists
		}
		delete(m.names, channel.Name)
		m.names[name] = true
		channel.Name = name
	}

	if description != "" {
		channel.Description = description
	}

	channel.IsPublic = isPublic
	channel.MaxUsers = maxUsers
	channel.UpdatedAt = time.Now()

	return channel, nil
}

// IsUserInChannel checks if user is in channel
func (m *Manager) IsUserInChannel(channelID string, userID string) bool {
	m.mu.RLock()
	defer m.mu.RUnlock()

	channel, exists := m.channels[channelID]
	if !exists {
		return false
	}

	return channel.IsMember(userID)
}

// GetUserChannels returns all channels a user is in
func (m *Manager) GetUserChannels(userID string) []*Channel {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var channels []*Channel
	for _, ch := range m.channels {
		if ch.Status == StatusActive && ch.IsMember(userID) {
			channels = append(channels, ch)
		}
	}
	return channels
}
