using Xunit;
using FluentAssertions;
using IamService.Domain.AggregatesModel.OrganizationAggregate;
using IamService.Domain.Events;

namespace IamService.UnitTests.Domain.Organizations;

/// <summary>
/// EN: Comprehensive unit tests for Organization aggregate root.
/// VI: Kiểm thử đơn vị toàn diện cho Organization aggregate root.
/// </summary>
public class OrganizationTests
{
    #region Creation Tests

    [Fact]
    public void Create_ValidParameters_CreatesOrganizationWithActiveStatus()
    {
        // Arrange
        var name = "Acme Corporation";
        var slug = "acme-corp";
        var description = "A sample organization";

        // Act
        var org = Organization.Create(name, slug, description);

        // Assert
        org.Should().NotBeNull();
        org.Id.Should().NotBeEmpty();
        org.Name.Should().Be(name);
        org.Slug.Should().Be(slug);
        org.Description.Should().Be(description);
        org.Status.Should().Be(OrganizationStatus.Active);
        org.StatusId.Should().Be(OrganizationStatus.Active.Id);
        org.ParentOrganizationId.Should().BeNull();
        org.Settings.Should().NotBeNull();
        org.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        org.UpdatedAt.Should().BeNull();
    }

    [Fact]
    public void Create_WithParentOrganization_SetsParentId()
    {
        // Arrange
        var parentId = Guid.NewGuid();

        // Act
        var org = Organization.Create("Child Org", "child-org", parentOrganizationId: parentId);

        // Assert
        org.ParentOrganizationId.Should().Be(parentId);
    }

    [Fact]
    public void Create_WithCustomSettings_UsesProvidedSettings()
    {
        // Arrange
        var customSettings = OrganizationSettings.Enterprise;

        // Act
        var org = Organization.Create("Enterprise Org", "enterprise-org", settings: customSettings);

        // Assert
        org.Settings.Should().Be(customSettings);
        org.Settings.Require2FA.Should().BeTrue();
    }

    [Fact]
    public void Create_WithoutSettings_UsesDefaultSettings()
    {
        // Arrange & Act
        var org = Organization.Create("Default Org", "default-org");

        // Assert
        org.Settings.AllowUserRegistration.Should().BeTrue();
        org.Settings.RequireEmailVerification.Should().BeTrue();
        org.Settings.Require2FA.Should().BeFalse();
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Create_InvalidName_ThrowsArgumentException(string? name)
    {
        // Arrange & Act
        var act = () => Organization.Create(name!, "valid-slug");

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*name*empty*");
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Create_InvalidSlug_ThrowsArgumentException(string? slug)
    {
        // Arrange & Act
        var act = () => Organization.Create("Valid Name", slug!);

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*slug*empty*");
    }

    [Theory]
    [InlineData("Invalid Slug")] // Contains space
    [InlineData("Invalid_Slug")] // Contains underscore
    [InlineData("InvalidSlug")]  // Contains uppercase
    [InlineData("invalid.slug")] // Contains dot
    [InlineData("-invalid")]     // Starts with hyphen
    [InlineData("invalid-")]     // Ends with hyphen
    public void Create_InvalidSlugFormat_ThrowsArgumentException(string slug)
    {
        // Arrange & Act
        var act = () => Organization.Create("Valid Name", slug);

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*lowercase letters, numbers, and hyphens*");
    }

    [Theory]
    [InlineData("valid-slug")]
    [InlineData("my-org")]
    [InlineData("test123")]
    [InlineData("org-1-2-3")]
    public void Create_ValidSlugFormat_CreatesOrganization(string slug)
    {
        // Arrange & Act
        var org = Organization.Create("Valid Name", slug);

        // Assert
        org.Slug.Should().Be(slug);
    }

    [Fact]
    public void Create_RaisesOrganizationCreatedEvent()
    {
        // Arrange & Act
        var org = Organization.Create("Test Org", "test-org");

        // Assert
        org.DomainEvents.Should().ContainSingle();
        org.DomainEvents.First().Should().BeOfType<OrganizationCreatedEvent>();
        var domainEvent = (OrganizationCreatedEvent)org.DomainEvents.First();
        domainEvent.OrganizationId.Should().Be(org.Id);
        domainEvent.Name.Should().Be("Test Org");
        domainEvent.Slug.Should().Be("test-org");
    }

    #endregion

    #region UpdateInfo Tests

    [Fact]
    public void UpdateInfo_ValidData_UpdatesNameAndDescription()
    {
        // Arrange
        var org = Organization.Create("Old Name", "old-slug", "Old Description");
        var newName = "New Name";
        var newDescription = "New Description";

        // Act
        org.UpdateInfo(newName, newDescription);

        // Assert
        org.Name.Should().Be(newName);
        org.Description.Should().Be(newDescription);
        org.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void UpdateInfo_RaisesOrganizationUpdatedEvent()
    {
        // Arrange
        var org = Organization.Create("Old Name", "old-slug");
        org.ClearDomainEvents();

        // Act
        org.UpdateInfo("New Name", null);

        // Assert
        org.DomainEvents.Should().ContainSingle();
        org.DomainEvents.First().Should().BeOfType<OrganizationUpdatedEvent>();
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void UpdateInfo_InvalidName_ThrowsArgumentException(string? name)
    {
        // Arrange
        var org = Organization.Create("Old Name", "old-slug");

        // Act
        var act = () => org.UpdateInfo(name!, "Description");

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*name*empty*");
    }

    #endregion

    #region UpdateSlug Tests

    [Fact]
    public void UpdateSlug_ValidSlug_UpdatesSlug()
    {
        // Arrange
        var org = Organization.Create("Test", "old-slug");
        var newSlug = "new-slug";

        // Act
        org.UpdateSlug(newSlug);

        // Assert
        org.Slug.Should().Be(newSlug);
        org.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void UpdateSlug_InvalidSlug_ThrowsArgumentException(string? slug)
    {
        // Arrange
        var org = Organization.Create("Test", "old-slug");

        // Act
        var act = () => org.UpdateSlug(slug!);

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*slug*empty*");
    }

    [Fact]
    public void UpdateSlug_InvalidFormat_ThrowsArgumentException()
    {
        // Arrange
        var org = Organization.Create("Test", "old-slug");

        // Act
        var act = () => org.UpdateSlug("Invalid Slug!");

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*lowercase letters, numbers, and hyphens*");
    }

    #endregion

    #region SetParent Tests

    [Fact]
    public void SetParent_ValidParentId_SetsParent()
    {
        // Arrange
        var org = Organization.Create("Child", "child");
        var parentId = Guid.NewGuid();

        // Act
        org.SetParent(parentId);

        // Assert
        org.ParentOrganizationId.Should().Be(parentId);
        org.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void SetParent_NullParentId_ClearsParent()
    {
        // Arrange
        var org = Organization.Create("Child", "child", parentOrganizationId: Guid.NewGuid());

        // Act
        org.SetParent(null);

        // Assert
        org.ParentOrganizationId.Should().BeNull();
    }

    [Fact]
    public void SetParent_SameAsOwnId_ThrowsInvalidOperationException()
    {
        // Arrange
        var org = Organization.Create("Self Parent", "self-parent");

        // Act
        var act = () => org.SetParent(org.Id);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*cannot be its own parent*");
    }

    #endregion

    #region UpdateSettings Tests

    [Fact]
    public void UpdateSettings_ValidSettings_UpdatesSettings()
    {
        // Arrange
        var org = Organization.Create("Test", "test");
        var newSettings = OrganizationSettings.Enterprise;

        // Act
        org.UpdateSettings(newSettings);

        // Assert
        org.Settings.Should().Be(newSettings);
        org.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void UpdateSettings_NullSettings_ThrowsArgumentNullException()
    {
        // Arrange
        var org = Organization.Create("Test", "test");

        // Act
        var act = () => org.UpdateSettings(null!);

        // Assert
        act.Should().Throw<ArgumentNullException>();
    }

    #endregion

    #region Status Transition Tests

    [Fact]
    public void Activate_SuspendedOrg_SetsStatusActive()
    {
        // Arrange
        var org = Organization.Create("Test", "test");
        org.Suspend();

        // Act
        org.Activate();

        // Assert
        org.Status.Should().Be(OrganizationStatus.Active);
        org.StatusId.Should().Be(OrganizationStatus.Active.Id);
    }

    [Fact]
    public void Activate_ArchivedOrg_ThrowsInvalidOperationException()
    {
        // Arrange
        var org = Organization.Create("Test", "test");
        org.Archive();

        // Act
        var act = () => org.Activate();

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*archived*");
    }

    [Fact]
    public void Suspend_ActiveOrg_SetsStatusSuspended()
    {
        // Arrange
        var org = Organization.Create("Test", "test");

        // Act
        org.Suspend();

        // Assert
        org.Status.Should().Be(OrganizationStatus.Suspended);
        org.StatusId.Should().Be(OrganizationStatus.Suspended.Id);
    }

    [Fact]
    public void Suspend_ArchivedOrg_ThrowsInvalidOperationException()
    {
        // Arrange
        var org = Organization.Create("Test", "test");
        org.Archive();

        // Act
        var act = () => org.Suspend();

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*archived*");
    }

    [Fact]
    public void Archive_ActiveOrg_SetsStatusArchived()
    {
        // Arrange
        var org = Organization.Create("Test", "test");

        // Act
        org.Archive();

        // Assert
        org.Status.Should().Be(OrganizationStatus.Archived);
        org.StatusId.Should().Be(OrganizationStatus.Archived.Id);
        org.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Archive_SuspendedOrg_SetsStatusArchived()
    {
        // Arrange
        var org = Organization.Create("Test", "test");
        org.Suspend();

        // Act
        org.Archive();

        // Assert
        org.Status.Should().Be(OrganizationStatus.Archived);
    }

    #endregion
}
