using MiningService.Domain.Events;
using MiningService.Domain.Exceptions;
using MiningService.Domain.SeedWork;

namespace MiningService.Domain.AggregatesModel.MinerAggregate;

/// <summary>
/// EN: Miner Aggregate Root - represents a user's mining profile.
/// VI: Miner Aggregate Root - đại diện cho hồ sơ đào của người dùng.
/// </summary>
public class Miner : Entity, IAggregateRoot
{
    private readonly List<MiningHistory> _miningHistories = new();

    #region Properties

    /// <summary>EN: User ID from IAM Service / VI: User ID từ IAM Service</summary>
    public Guid UserId { get; private set; }

    /// <summary>EN: User's mining role / VI: Vai trò đào của người dùng</summary>
    public MinerRole Role { get; private set; }

    /// <summary>EN: Total mining points accumulated / VI: Tổng điểm đào tích lũy</summary>
    public decimal TotalMinedPoints { get; private set; }

    /// <summary>EN: Current mining rate / VI: Tỷ lệ đào hiện tại</summary>
    public MiningRate CurrentRate { get; private set; } = MiningRate.CreateDefault();

    /// <summary>EN: Current active mining session / VI: Phiên đào đang hoạt động</summary>
    public MiningSession? ActiveSession { get; private set; }

    /// <summary>EN: Mining streak tracking / VI: Theo dõi streak đào</summary>
    public MiningStreak Streak { get; private set; } = MiningStreak.CreateNew();

    /// <summary>EN: Unique referral code for this miner / VI: Mã giới thiệu duy nhất</summary>
    public string ReferralCode { get; private set; } = string.Empty;

    /// <summary>EN: ID of miner who referred this user / VI: ID thợ đào giới thiệu người này</summary>
    public Guid? ReferredById { get; private set; }

    /// <summary>EN: Security circle ID if member / VI: ID vòng tròn an toàn nếu là thành viên</summary>
    public Guid? CircleId { get; private set; }

    /// <summary>EN: Account status / VI: Trạng thái tài khoản</summary>
    public MinerStatus Status { get; private set; }

    /// <summary>EN: When account was created / VI: Khi tài khoản được tạo</summary>
    public DateTime CreatedAt { get; private set; }

    /// <summary>EN: Last update time / VI: Thời gian cập nhật cuối</summary>
    public DateTime UpdatedAt { get; private set; }

    /// <summary>EN: Concurrency token / VI: Token đồng thời</summary>
    public byte[] RowVersion { get; private set; } = Array.Empty<byte>();

    /// <summary>EN: Mining history / VI: Lịch sử đào</summary>
    public IReadOnlyCollection<MiningHistory> MiningHistories => _miningHistories.AsReadOnly();

    #endregion

    // EF Core constructor
    protected Miner() { }

    #region Factory Methods

    /// <summary>
    /// EN: Create a new miner profile.
    /// VI: Tạo hồ sơ thợ đào mới.
    /// </summary>
    public static Miner Create(Guid userId, string? referralCode = null, Guid? referredById = null)
    {
        var miner = new Miner
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            Role = MinerRole.Pioneer,
            TotalMinedPoints = 0,
            CurrentRate = MiningRate.CreateDefault(),
            ActiveSession = null,
            Streak = MiningStreak.CreateNew(),
            ReferralCode = GenerateReferralCode(),
            ReferredById = referredById,
            CircleId = null,
            Status = MinerStatus.Active,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };

        miner.AddDomainEvent(new MinerCreatedDomainEvent(miner.Id, userId));
        return miner;
    }

    #endregion

    #region Mining Session Methods

    /// <summary>
    /// EN: Start a new mining session.
    /// VI: Bắt đầu phiên đào mới.
    /// </summary>
    public MiningSession StartMiningSession(decimal configBaseRate = 0.25m, int sessionHours = 24)
    {
        if (Status != MinerStatus.Active)
            throw new MiningDomainException("Cannot start mining: account is not active");

        if (ActiveSession != null && ActiveSession.Status == MiningSessionStatus.Active)
            throw new MiningDomainException("Cannot start mining: active session exists");

        // Recalculate rate with current bonuses
        RecalculateRate(configBaseRate);

        // Create new session
        ActiveSession = MiningSession.Create(CurrentRate.TotalRate, sessionHours);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new MiningSessionStartedDomainEvent(Id, ActiveSession.SessionId, CurrentRate.TotalRate));
        return ActiveSession;
    }

    /// <summary>
    /// EN: Claim mining reward from completed session.
    /// VI: Nhận thưởng đào từ phiên hoàn thành.
    /// </summary>
    public decimal ClaimMiningReward()
    {
        if (ActiveSession == null)
            throw new MiningDomainException("No active session to claim");

        if (!ActiveSession.IsReadyToClaim)
            throw new MiningDomainException("Session is not ready to claim yet");

        // Calculate earned points
        var earnedPoints = ActiveSession.CalculateEarnedPoints();

        // Update streak
        var previousStreak = Streak.CurrentStreak;
        Streak = Streak.IncrementStreak();

        // Check for milestone rewards
        var milestoneBonus = CalculateMilestoneBonus(Streak.CurrentStreak, previousStreak);
        var totalPoints = earnedPoints + milestoneBonus;

        // Add to total
        TotalMinedPoints += totalPoints;

        // Create history entry
        var history = MiningHistory.CreateFromSession(
            Id,
            earnedPoints,
            ActiveSession.SessionId,
            ActiveSession.HourlyRate,
            Streak.CurrentStreak
        );
        _miningHistories.Add(history);

        // If milestone bonus, create separate entry
        if (milestoneBonus > 0)
        {
            var bonusHistory = MiningHistory.CreateFromBonus(Id, milestoneBonus, $"Streak Milestone Day {Streak.CurrentStreak}");
            _miningHistories.Add(bonusHistory);
        }

        // Mark session as claimed
        ActiveSession = ActiveSession.MarkAsClaimed(earnedPoints);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new PointsMinedDomainEvent(Id, totalPoints, TotalMinedPoints, Streak.CurrentStreak));
        return totalPoints;
    }

    #endregion

    #region Rate Calculation

    /// <summary>
    /// EN: Recalculate mining rate based on current bonuses.
    /// VI: Tính lại tỷ lệ đào dựa trên các thưởng hiện tại.
    /// </summary>
    public void RecalculateRate(
        decimal baseRate = 0.25m,
        decimal circleBonus = 0,
        decimal referralBonus = 0)
    {
        var roleBonus = Role switch
        {
            MinerRole.Pioneer => 0m,
            MinerRole.Contributor => 0.10m,
            MinerRole.Ambassador => 0.25m,
            MinerRole.NodeOperator => 0.50m,
            _ => 0m
        };

        CurrentRate = new MiningRate
        {
            BaseRate = baseRate,
            RoleBonus = roleBonus,
            CircleBonus = circleBonus,
            ReferralBonus = referralBonus,
            StreakBonus = Streak.BonusMultiplier
        };

        UpdatedAt = DateTime.UtcNow;
    }

    #endregion

    #region Role & Status Management

    /// <summary>
    /// EN: Upgrade miner role.
    /// VI: Nâng cấp vai trò thợ đào.
    /// </summary>
    public void UpgradeRole(MinerRole newRole)
    {
        if (newRole <= Role)
            throw new MiningDomainException("Cannot downgrade role");

        Role = newRole;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Join a security circle.
    /// VI: Tham gia vòng tròn an toàn.
    /// </summary>
    public void JoinCircle(Guid circleId)
    {
        if (CircleId.HasValue)
            throw new MiningDomainException("Already in a circle");

        CircleId = circleId;
        
        // Upgrade to Contributor if still Pioneer
        if (Role == MinerRole.Pioneer)
            Role = MinerRole.Contributor;

        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Leave current security circle.
    /// VI: Rời vòng tròn an toàn hiện tại.
    /// </summary>
    public void LeaveCircle()
    {
        CircleId = null;
        
        // Downgrade from Contributor if no other qualifications
        if (Role == MinerRole.Contributor)
            Role = MinerRole.Pioneer;

        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Suspend miner account.
    /// VI: Tạm ngừng tài khoản thợ đào.
    /// </summary>
    public void Suspend()
    {
        if (Status == MinerStatus.Banned)
            throw new MiningDomainException("Cannot suspend banned account");

        Status = MinerStatus.Suspended;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Restore suspended account.
    /// VI: Khôi phục tài khoản bị tạm ngừng.
    /// </summary>
    public void Restore()
    {
        if (Status != MinerStatus.Suspended)
            throw new MiningDomainException("Can only restore suspended accounts");

        Status = MinerStatus.Active;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Ban miner account permanently.
    /// VI: Cấm tài khoản thợ đào vĩnh viễn.
    /// </summary>
    public void Ban()
    {
        Status = MinerStatus.Banned;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Reset miner's mining streak (admin action).
    /// VI: Reset streak đào của thợ đào (hành động admin).
    /// </summary>
    public void ResetStreak()
    {
        Streak = Streak.Reset();
        UpdatedAt = DateTime.UtcNow;
    }

    #endregion

    #region Points Management

    /// <summary>
    /// EN: Add bonus points (from admin, referral, etc).
    /// VI: Thêm điểm thưởng (từ admin, giới thiệu, v.v.).
    /// </summary>
    public void AddBonusPoints(decimal points, string source)
    {
        if (points <= 0)
            throw new MiningDomainException("Bonus points must be positive");

        TotalMinedPoints += points;
        
        var history = MiningHistory.CreateFromBonus(Id, points, source);
        _miningHistories.Add(history);

        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Deduct points (for recovery, purchases, etc).
    /// VI: Trừ điểm (cho khôi phục, mua sắm, v.v.).
    /// </summary>
    public void DeductPoints(decimal points, string reason)
    {
        if (points <= 0)
            throw new MiningDomainException("Deduction must be positive");

        if (TotalMinedPoints < points)
            throw new MiningDomainException("Insufficient points");

        TotalMinedPoints -= points;
        
        var history = MiningHistory.CreateFromBonus(Id, -points, reason);
        _miningHistories.Add(history);

        UpdatedAt = DateTime.UtcNow;
    }

    #endregion

    #region Private Helpers

    private static string GenerateReferralCode()
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        var random = new Random();
        return new string(Enumerable.Repeat(chars, 8).Select(s => s[random.Next(s.Length)]).ToArray());
    }

    private static decimal CalculateMilestoneBonus(int newStreak, int previousStreak)
    {
        decimal bonus = 0;

        // Check milestones crossed
        if (newStreak >= 3 && previousStreak < 3) bonus += 0; // Badge only
        if (newStreak >= 7 && previousStreak < 7) bonus += 50;
        if (newStreak >= 14 && previousStreak < 14) bonus += 100;
        if (newStreak >= 30 && previousStreak < 30) bonus += 300;
        if (newStreak >= 60 && previousStreak < 60) bonus += 500;
        if (newStreak >= 90 && previousStreak < 90) bonus += 1000;

        return bonus;
    }

    #endregion
}
