using Knot.Shared.Kernel;

namespace Knot.Contracts.Conversations.Domain;

public sealed record ChatCreatedDomainEvent(Chat Chat) : IDomainEvent;
public sealed record ChatMemberAddedDomainEvent(Guid ChatId, Guid UserId) : IDomainEvent;

/// <summary>
/// Тип чата: личный или групповой.
/// </summary>
public enum ChatType
{
    Personal,
    Group,
    Favorites
}

/// <summary>
/// Роль участника в чате.
/// </summary>
public static class ChatRole
{
    public const string Owner = "owner";
    public const string Admin = "admin";
    public const string Member = "member";
}

/// <summary>
/// Сущность чата (Агрегат).
/// </summary>
public sealed class Chat : AggregateRoot<Guid>
{
    public ChatType Type { get; private set; }
    public string? Name { get; private set; }
    public string? Description { get; private set; }
    public string? Avatar { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public long LastMessageSequenceId { get; private set; }
    public bool IsImporting { get; private set; }
    public Guid? ImportJobId { get; private set; }

    private readonly List<ChatMember> _members = new();
    public IReadOnlyCollection<ChatMember> Members => _members.AsReadOnly();

    private Chat(Guid id, ChatType type, string? name, string? avatar, string? description = null, bool isImporting = false, Guid? importJobId = null) : base(id)
    {
        Type = type;
        Name = name;
        Avatar = avatar;
        Description = description;
        CreatedAt = DateTime.UtcNow;
        IsImporting = isImporting;
        ImportJobId = importJobId;
    }

    public static Chat CreatePersonal()
    {
        var chat = new Chat(Guid.NewGuid(), ChatType.Personal, null, null);
        chat.RaiseDomainEvent(new ChatCreatedDomainEvent(chat));
        return chat;
    }

    public static Chat CreateGroup(string name, string? avatar = null)
    {
        var chat = new Chat(Guid.NewGuid(), ChatType.Group, name, avatar);
        chat.RaiseDomainEvent(new ChatCreatedDomainEvent(chat));
        return chat;
    }

    public static Chat Create(string? name, ChatType type, string? avatar = null, string? description = null, bool isImporting = false, Guid? importJobId = null)
    {
        var chat = new Chat(Guid.NewGuid(), type, name, avatar, description, isImporting, importJobId);
        chat.RaiseDomainEvent(new ChatCreatedDomainEvent(chat));
        return chat;
    }

    public void CompleteImport()
    {
        IsImporting = false;
    }

    public void AddMember(Guid userId, string role = "member")
    {
        if (_members.Any(m => m.UserId == userId))
        {
            return;
        }

        _members.Add(new ChatMember(Id, userId, role));
        RaiseDomainEvent(new ChatMemberAddedDomainEvent(Id, userId));
    }

    public void RemoveMember(Guid userId)
    {
        var member = _members.FirstOrDefault(m => m.UserId == userId);
        if (member != null)
        {
            _members.Remove(member);
        }
    }

    public void UpdateName(string name) => Name = name;

    public void UpdateDescription(string? description) => Description = description;

    public void UpdateAvatar(string? avatarUrl) => Avatar = avatarUrl;

    public long IncrementSequenceId()
    {
        return ++LastMessageSequenceId;
    }
}

/// <summary>
/// Участник чата.
/// </summary>
public sealed class ChatMember : Entity<Guid>
{
    public Guid ChatId { get; private set; }
    public Guid UserId { get; private set; }
    public string Role { get; private set; }
    public DateTime JoinedAt { get; private set; }
    public bool IsPinned { get; private set; }
    public bool IsMuted { get; private set; }
    
    public Guid? LastReadMessageId { get; private set; }
    public long LastReadSequenceId { get; private set; }
    public Guid? LastDeliveredMessageId { get; private set; }

    private ChatMember() : base(Guid.Empty) { Role = "member"; }

    internal ChatMember(Guid chatId, Guid userId, string role) : base(Guid.NewGuid())
    {
        ChatId = chatId;
        UserId = userId;
        Role = role;
        JoinedAt = DateTime.UtcNow;
    }

    public void TogglePin() => IsPinned = !IsPinned;

    public void UpdateReadCursor(Guid messageId, long sequenceId)
    {
        if (sequenceId > LastReadSequenceId)
        {
            LastReadMessageId = messageId;
            LastReadSequenceId = sequenceId;
        }
    }

    public void UpdateDeliveredCursor(Guid messageId)
    {
        LastDeliveredMessageId = messageId;
    }
}
