using FluentAssertions;
using MerchantService.Domain.AggregatesModel.ShopAggregate;
using MerchantService.Domain.Events;
using MerchantService.Domain.Exceptions;
using Xunit;

namespace MerchantService.UnitTests.Domain;

/// <summary>
/// EN: Unit tests for Shop aggregate root — creation, updates, status lifecycle, and branches.
/// VI: Unit tests cho aggregate root Shop — tạo mới, cập nhật, vòng đời trạng thái, và chi nhánh.
/// </summary>
public class ShopAggregateTests
{
    // ──────── HELPERS ────────

    private static Shop CreateValidShop(
        ShopType? type = null,
        BusinessCategory? category = null)
    {
        return new Shop(
            Guid.NewGuid(),
            "Test Coffee Shop",
            "test-coffee-shop",
            type ?? ShopType.Hybrid,
            category ?? BusinessCategory.Cafe);
    }

    private static Address CreateTestAddress() => new()
    {
        Street = "123 Nguyễn Huệ",
        District = "Quận 1",
        City = "Hồ Chí Minh",
        PostalCode = "700000",
        CountryCode = "VN"
    };

    // ──────── CREATION TESTS ────────

    [Fact]
    public void CreateShop_WithValidData_ShouldStartInDraftStatus()
    {
        // Act
        var shop = CreateValidShop();

        // Assert
        shop.Id.Should().NotBe(Guid.Empty);
        shop.Name.Should().Be("Test Coffee Shop");
        shop.Slug.Should().Be("test-coffee-shop");
        shop.Status.Should().Be(ShopStatus.Draft);
        shop.IsDeleted.Should().BeFalse();
        shop.Branches.Should().BeEmpty();
    }

    [Fact]
    public void CreateShop_ShouldRaiseShopCreatedDomainEvent()
    {
        // Act
        var shop = CreateValidShop();

        // Assert
        shop.DomainEvents.Should().ContainSingle()
            .Which.Should().BeOfType<ShopCreatedDomainEvent>();
    }

    [Fact]
    public void CreateShop_WithCafeCategory_ShouldAssignFnBFeatures()
    {
        // Act
        var shop = CreateValidShop(category: BusinessCategory.Cafe);

        // Assert
        shop.Features.HasInventory.Should().BeTrue();
        shop.Features.HasTables.Should().BeTrue();
        shop.Features.HasKitchen.Should().BeTrue();
        shop.Features.HasDelivery.Should().BeTrue();
        shop.Features.HasBooking.Should().BeFalse();
    }

    [Fact]
    public void CreateShop_WithEmptyMerchantId_ShouldThrowDomainException()
    {
        // Act
        var act = () => new Shop(Guid.Empty, "Shop", "shop", ShopType.Hybrid, BusinessCategory.FoodBeverage);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Merchant ID cannot be empty");
    }

    [Fact]
    public void CreateShop_WithEmptyName_ShouldThrowDomainException()
    {
        // Act
        var act = () => new Shop(Guid.NewGuid(), "", "shop", ShopType.Hybrid, BusinessCategory.FoodBeverage);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Shop name cannot be empty");
    }

    [Fact]
    public void CreateShop_WithInvalidSlug_ShouldThrowDomainException()
    {
        // Act
        var act = () => new Shop(
            Guid.NewGuid(), "Shop", "INVALID SLUG!", ShopType.Hybrid, BusinessCategory.FoodBeverage);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Slug must contain only lowercase letters, numbers, and hyphens");
    }

    // ──────── UPDATE TESTS ────────

    [Fact]
    public void UpdateInfo_ShouldUpdateNameAndDescription()
    {
        // Arrange
        var shop = CreateValidShop();

        // Act
        shop.UpdateInfo("New Name", "New desc");

        // Assert
        shop.Name.Should().Be("New Name");
        shop.Description.Should().Be("New desc");
        shop.UpdatedAt.Should().NotBeNull();
    }

    [Fact]
    public void UpdateSlug_WithValidSlug_ShouldUpdateSuccessfully()
    {
        // Arrange
        var shop = CreateValidShop();

        // Act
        shop.UpdateSlug("new-slug-123");

        // Assert
        shop.Slug.Should().Be("new-slug-123");
    }

    [Fact]
    public void UpdateSlug_WithInvalidSlug_ShouldThrowDomainException()
    {
        // Arrange
        var shop = CreateValidShop();

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

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

    [Fact]
    public void UpdateFeatures_ShouldOverrideFeatures()
    {
        // Arrange
        var shop = CreateValidShop();
        var newFeatures = new ShopFeatures { HasBooking = true, HasShipping = true };

        // Act
        shop.UpdateFeatures(newFeatures);

        // Assert
        shop.Features.HasBooking.Should().BeTrue();
        shop.Features.HasShipping.Should().BeTrue();
        shop.Features.HasTables.Should().BeFalse(); // Overridden from Cafe defaults
    }

    [Fact]
    public void UpdateImages_ShouldSetLogoAndCover()
    {
        // Arrange
        var shop = CreateValidShop();

        // Act
        shop.UpdateImages("https://cdn/logo.png", "https://cdn/cover.png");

        // Assert
        shop.LogoUrl.Should().Be("https://cdn/logo.png");
        shop.CoverImageUrl.Should().Be("https://cdn/cover.png");
    }

    // ──────── STATUS LIFECYCLE TESTS ────────

    [Fact]
    public void Publish_DraftShop_ShouldSetStatusToActive()
    {
        // Arrange
        var shop = CreateValidShop();

        // Act
        shop.Publish();

        // Assert
        shop.Status.Should().Be(ShopStatus.Active);
        shop.DomainEvents.Should().Contain(e => e is ShopPublishedDomainEvent);
    }

    [Fact]
    public void Publish_ActiveShop_ShouldThrowDomainException()
    {
        // Arrange
        var shop = CreateValidShop();
        shop.Publish(); // Already active

        // Act
        var act = () => shop.Publish();

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Cannot publish shop with status Active");
    }

    [Fact]
    public void SetInactive_ActiveShop_ShouldSetStatusInactive()
    {
        // Arrange
        var shop = CreateValidShop();
        shop.Publish();

        // Act
        shop.SetInactive();

        // Assert
        shop.Status.Should().Be(ShopStatus.Inactive);
    }

    [Fact]
    public void SetInactive_ClosedShop_ShouldThrowDomainException()
    {
        // Arrange
        var shop = CreateValidShop();
        shop.Close();

        // Act
        var act = () => shop.SetInactive();

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Cannot change status of closed shop");
    }

    [Fact]
    public void Close_ShouldSetStatusClosed()
    {
        // Arrange
        var shop = CreateValidShop();
        shop.Publish();

        // Act
        shop.Close();

        // Assert
        shop.Status.Should().Be(ShopStatus.Closed);
        shop.DomainEvents.Should().Contain(e => e is ShopClosedDomainEvent);
    }

    // ──────── BRANCH TESTS ────────

    [Fact]
    public void AddBranch_HybridShop_ShouldAddSuccessfully()
    {
        // Arrange
        var shop = CreateValidShop(type: ShopType.Hybrid);
        var address = CreateTestAddress();

        // Act
        var branch = shop.AddBranch("Chi nhánh Quận 1", address);

        // Assert
        shop.Branches.Should().HaveCount(1);
        branch.Name.Should().Be("Chi nhánh Quận 1");
        branch.IsActive.Should().BeTrue();
        shop.DomainEvents.Should().Contain(e => e is ShopBranchAddedDomainEvent);
    }

    [Fact]
    public void AddBranch_OnlineOnlyShop_ShouldThrowDomainException()
    {
        // Arrange
        var shop = CreateValidShop(type: ShopType.OnlineOnly);
        var address = CreateTestAddress();

        // Act
        var act = () => shop.AddBranch("Branch 1", address);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Online-only shops cannot have physical branches");
    }

    [Fact]
    public void RemoveBranch_ExistingBranch_ShouldRemoveSuccessfully()
    {
        // Arrange
        var shop = CreateValidShop(type: ShopType.PhysicalOnly);
        var branch = shop.AddBranch("Branch 1", CreateTestAddress());
        var branchId = branch.Id;

        // Act
        shop.RemoveBranch(branchId);

        // Assert
        shop.Branches.Should().BeEmpty();
    }

    [Fact]
    public void RemoveBranch_NonExistingBranch_ShouldThrowDomainException()
    {
        // Arrange
        var shop = CreateValidShop();

        // Act
        var act = () => shop.RemoveBranch(Guid.NewGuid());

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Branch not found");
    }

    // ──────── SOFT DELETE TESTS ────────

    [Fact]
    public void Delete_ShouldSoftDeleteShop()
    {
        // Arrange
        var shop = CreateValidShop();

        // Act
        shop.Delete();

        // Assert
        shop.IsDeleted.Should().BeTrue();
    }

    [Fact]
    public void Delete_AlreadyDeletedShop_ShouldThrowDomainException()
    {
        // Arrange
        var shop = CreateValidShop();
        shop.Delete();

        // Act
        var act = () => shop.Delete();

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Shop is already deleted");
    }
}
