using ColaFlow.Shared.Kernel.Common;
using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants.Events;

namespace ColaFlow.Modules.Identity.Domain.Aggregates.Tenants;

/// <summary>
/// Tenant aggregate root - represents a single organization/company in the system
/// </summary>
public sealed class Tenant : AggregateRoot
{
    // Properties
    public TenantName Name { get; private set; } = null!;
    public TenantSlug Slug { get; private set; } = null!;
    public TenantStatus Status { get; private set; }
    public SubscriptionPlan Plan { get; private set; }
    public SsoConfiguration? SsoConfig { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public DateTime? UpdatedAt { get; private set; }
    public DateTime? SuspendedAt { get; private set; }
    public string? SuspensionReason { get; private set; }

    // Settings
    public int MaxUsers { get; private set; }
    public int MaxProjects { get; private set; }
    public int MaxStorageGB { get; private set; }

    // Private constructor for EF Core
    private Tenant() : base()
    {
    }

    // Factory method for creating new tenant
    public static Tenant Create(
        TenantName name,
        TenantSlug slug,
        SubscriptionPlan plan = SubscriptionPlan.Free)
    {
        var tenant = new Tenant
        {
            Id = Guid.NewGuid(),
            Name = name,
            Slug = slug,
            Status = TenantStatus.Active,
            Plan = plan,
            CreatedAt = DateTime.UtcNow,
            MaxUsers = GetMaxUsersByPlan(plan),
            MaxProjects = GetMaxProjectsByPlan(plan),
            MaxStorageGB = GetMaxStorageByPlan(plan)
        };

        tenant.AddDomainEvent(new TenantCreatedEvent(tenant.Id, tenant.Slug));

        return tenant;
    }

    // Business methods
    public void UpdateName(TenantName newName)
    {
        if (Status == TenantStatus.Cancelled)
            throw new InvalidOperationException("Cannot update cancelled tenant");

        Name = newName;
        UpdatedAt = DateTime.UtcNow;
    }

    public void UpgradePlan(SubscriptionPlan newPlan)
    {
        if (newPlan <= Plan)
            throw new InvalidOperationException("New plan must be higher than current plan");

        if (Status != TenantStatus.Active && Status != TenantStatus.Trial)
            throw new InvalidOperationException("Only active or trial tenants can upgrade");

        Plan = newPlan;
        MaxUsers = GetMaxUsersByPlan(newPlan);
        MaxProjects = GetMaxProjectsByPlan(newPlan);
        MaxStorageGB = GetMaxStorageByPlan(newPlan);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new TenantPlanUpgradedEvent(Id, newPlan));
    }

    public void ConfigureSso(SsoConfiguration ssoConfig)
    {
        if (Plan == SubscriptionPlan.Free || Plan == SubscriptionPlan.Starter)
            throw new InvalidOperationException("SSO is only available for Professional and Enterprise plans");

        SsoConfig = ssoConfig;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new SsoConfiguredEvent(Id, ssoConfig.Provider));
    }

    public void DisableSso()
    {
        if (SsoConfig == null)
            throw new InvalidOperationException("SSO is not configured");

        SsoConfig = null;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new SsoDisabledEvent(Id));
    }

    public void Activate()
    {
        if (Status == TenantStatus.Cancelled)
            throw new InvalidOperationException("Cannot activate cancelled tenant");

        if (Status == TenantStatus.Active)
            return; // Already active

        Status = TenantStatus.Active;
        SuspendedAt = null;
        SuspensionReason = null;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new TenantActivatedEvent(Id));
    }

    public void Suspend(string reason)
    {
        if (Status == TenantStatus.Cancelled)
            throw new InvalidOperationException("Cannot suspend cancelled tenant");

        if (Status == TenantStatus.Suspended)
            return; // Already suspended

        Status = TenantStatus.Suspended;
        SuspendedAt = DateTime.UtcNow;
        SuspensionReason = reason;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new TenantSuspendedEvent(Id, reason));
    }

    public void Cancel()
    {
        if (Status == TenantStatus.Cancelled)
            return; // Already cancelled

        Status = TenantStatus.Cancelled;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new TenantCancelledEvent(Id));
    }

    // Plan limits
    private static int GetMaxUsersByPlan(SubscriptionPlan plan) => plan switch
    {
        SubscriptionPlan.Free => 5,
        SubscriptionPlan.Starter => 20,
        SubscriptionPlan.Professional => 100,
        SubscriptionPlan.Enterprise => int.MaxValue,
        _ => throw new ArgumentOutOfRangeException(nameof(plan))
    };

    private static int GetMaxProjectsByPlan(SubscriptionPlan plan) => plan switch
    {
        SubscriptionPlan.Free => 3,
        SubscriptionPlan.Starter => 20,
        SubscriptionPlan.Professional => 100,
        SubscriptionPlan.Enterprise => int.MaxValue,
        _ => throw new ArgumentOutOfRangeException(nameof(plan))
    };

    private static int GetMaxStorageByPlan(SubscriptionPlan plan) => plan switch
    {
        SubscriptionPlan.Free => 2,
        SubscriptionPlan.Starter => 20,
        SubscriptionPlan.Professional => 100,
        SubscriptionPlan.Enterprise => 1000,
        _ => throw new ArgumentOutOfRangeException(nameof(plan))
    };
}
