using FluentAssertions;
using Microsoft.Extensions.Time.Testing;
using SIGCM2.Domain.Pricing.ChargeableChars;
using SIGCM2.Domain.Pricing.Exceptions;

namespace SIGCM2.Application.Tests.Domain.Pricing.ChargeableChars;

/// <summary>
/// PRC-001 — T2.3 Domain unit tests for ChargeableCharConfig entity.
/// Covers: factory invariants, ScheduleNewPrice forward-only, Deactivate idempotency.
/// </summary>
public sealed class ChargeableCharConfigTests
{
    // Fixed "today" for Argentina: 2026-04-20
    private static FakeTimeProvider MakeFakeTimeProvider(DateOnly argentinaToday)
    {
        var fp = new FakeTimeProvider();
        // Argentina is UTC-3. Set UTC to 12:00 of that day → Argentina 09:00 = same day.
        fp.SetUtcNow(new DateTimeOffset(
            argentinaToday.Year, argentinaToday.Month, argentinaToday.Day,
            12, 0, 0, TimeSpan.Zero));
        return fp;
    }

    private static DateOnly Today => new DateOnly(2026, 4, 20);
    private static FakeTimeProvider FakeToday => MakeFakeTimeProvider(Today);

    // ── Create factory — invariant violations ─────────────────────────────────

    [Fact]
    public void Create_ThrowsInvalid_WhenSymbolIsEmpty()
    {
        var act = () => ChargeableCharConfig.Create(null, "", "Currency", 1.0m, Today);

        act.Should().Throw<ChargeableCharConfigInvalidException>()
            .Which.Field.Should().Be("Symbol");
    }

    [Fact]
    public void Create_ThrowsInvalid_WhenSymbolIsWhitespace()
    {
        var act = () => ChargeableCharConfig.Create(null, "   ", "Currency", 1.0m, Today);

        act.Should().Throw<ChargeableCharConfigInvalidException>()
            .Which.Field.Should().Be("Symbol");
    }

    [Fact]
    public void Create_ThrowsInvalid_WhenSymbolExceedsFourChars()
    {
        var act = () => ChargeableCharConfig.Create(null, "$$$$$", "Currency", 1.0m, Today);

        act.Should().Throw<ChargeableCharConfigInvalidException>()
            .Which.Field.Should().Be("Symbol");
    }

    [Fact]
    public void Create_ThrowsInvalid_WhenPricePerUnitIsZero()
    {
        var act = () => ChargeableCharConfig.Create(null, "$", "Currency", 0m, Today);

        act.Should().Throw<ChargeableCharConfigInvalidException>()
            .Which.Field.Should().Be("PricePerUnit");
    }

    [Fact]
    public void Create_ThrowsInvalid_WhenPricePerUnitIsNegative()
    {
        var act = () => ChargeableCharConfig.Create(null, "$", "Currency", -1.5m, Today);

        act.Should().Throw<ChargeableCharConfigInvalidException>()
            .Which.Field.Should().Be("PricePerUnit");
    }

    [Fact]
    public void Create_ThrowsInvalid_WhenCategoryIsUnknown()
    {
        var act = () => ChargeableCharConfig.Create(null, "$", "INVALID_CAT", 1.0m, Today);

        act.Should().Throw<ChargeableCharConfigInvalidException>()
            .Which.Field.Should().Be("Category");
    }

    // ── Create factory — happy path ───────────────────────────────────────────

    [Fact]
    public void Create_HappyPath_ReturnsEntityWithIsActiveTrue_AndIdZero()
    {
        var entity = ChargeableCharConfig.Create(null, "$", "Currency", 1.5m, Today);

        entity.Id.Should().Be(0);
        entity.IsActive.Should().BeTrue();
        entity.ValidTo.Should().BeNull();
        entity.Symbol.Should().Be("$");
        entity.Category.Should().Be("Currency");
        entity.PricePerUnit.Should().Be(1.5m);
        entity.ValidFrom.Should().Be(Today);
        entity.ProductTypeId.Should().BeNull();
    }

    [Fact]
    public void Create_WithProductTypeId_SetsCorrectly()
    {
        var entity = ChargeableCharConfig.Create(5, "$", "Currency", 2.0m, Today);

        entity.ProductTypeId.Should().Be(5);
    }

    [Fact]
    public void Create_FourCharSymbol_IsValid()
    {
        var act = () => ChargeableCharConfig.Create(null, "$$$$", "Currency", 1.0m, Today);

        act.Should().NotThrow();
    }

    // ── ScheduleNewPrice — forward-only ───────────────────────────────────────

    [Fact]
    public void ScheduleNewPrice_ThrowsForwardOnly_WhenNewValidFromEqualToCurrent()
    {
        var entity = ChargeableCharConfig.Create(null, "$", "Currency", 1.0m, Today);

        var act = () => entity.ScheduleNewPrice(2.0m, Today, FakeToday);

        act.Should().Throw<ChargeableCharConfigForwardOnlyException>();
    }

    [Fact]
    public void ScheduleNewPrice_ThrowsForwardOnly_WhenNewValidFromBeforeCurrent()
    {
        var entity = ChargeableCharConfig.Create(null, "$", "Currency", 1.0m, Today);
        var pastDate = Today.AddDays(-1);
        var fp = MakeFakeTimeProvider(Today.AddDays(-2)); // today is 2 days ago so pastDate is still "future" relative to fake today
        // But we want to test forward-only (new <= current ValidFrom), not past date
        // Use a fake today that makes pastDate pass the ">=today" check, but still fail forward-only
        var fpPast = MakeFakeTimeProvider(Today.AddDays(-10)); // today = Apr 10, pastDate = Apr 19 is future

        var act = () => entity.ScheduleNewPrice(2.0m, pastDate, fpPast);

        act.Should().Throw<ChargeableCharConfigForwardOnlyException>();
    }

    [Fact]
    public void ScheduleNewPrice_ThrowsInvalid_WhenNewValidFromBeforeTodayAR()
    {
        var entity = ChargeableCharConfig.Create(null, "$", "Currency", 1.0m, Today.AddDays(-10));
        // today in fake = Apr 20; newValidFrom = Apr 18 < today → invalid (past date)
        var pastDate = Today.AddDays(-2);

        var act = () => entity.ScheduleNewPrice(2.0m, pastDate, FakeToday);

        act.Should().Throw<ChargeableCharConfigInvalidException>()
            .Which.Field.Should().Be("ValidFrom");
    }

    [Fact]
    public void ScheduleNewPrice_HappyPath_ReturnsNewEntity_DoesNotMutateThis()
    {
        var original = ChargeableCharConfig.Create(null, "$", "Currency", 1.0m, Today);
        var futureDate = Today.AddDays(30);

        var newEntity = original.ScheduleNewPrice(2.0m, futureDate, FakeToday);

        // new entity has updated price and validFrom
        newEntity.PricePerUnit.Should().Be(2.0m);
        newEntity.ValidFrom.Should().Be(futureDate);
        newEntity.Symbol.Should().Be("$");
        newEntity.Category.Should().Be("Currency");
        newEntity.IsActive.Should().BeTrue();

        // original is unchanged (forward-only semantics)
        original.PricePerUnit.Should().Be(1.0m);
        original.ValidFrom.Should().Be(Today);
    }

    [Fact]
    public void ScheduleNewPrice_ThrowsInvalid_WhenNewPriceIsZero()
    {
        var entity = ChargeableCharConfig.Create(null, "$", "Currency", 1.0m, Today);
        var futureDate = Today.AddDays(5);

        var act = () => entity.ScheduleNewPrice(0m, futureDate, FakeToday);

        act.Should().Throw<ChargeableCharConfigInvalidException>();
    }

    // ── Deactivate ────────────────────────────────────────────────────────────

    [Fact]
    public void Deactivate_SetsIsActiveFalseAndValidToToday()
    {
        var entity = ChargeableCharConfig.Create(null, "$", "Currency", 1.0m, Today);

        entity.Deactivate(Today);

        entity.IsActive.Should().BeFalse();
        entity.ValidTo.Should().Be(Today);
    }

    [Fact]
    public void Deactivate_Idempotent_WhenAlreadyInactive()
    {
        var entity = ChargeableCharConfig.Create(null, "$", "Currency", 1.0m, Today);
        entity.Deactivate(Today);

        // Second deactivate on same entity — no exception, no change
        var act = () => entity.Deactivate(Today);

        act.Should().NotThrow();
        entity.IsActive.Should().BeFalse();
    }

    // ── Rehydrate (reconstructor) ─────────────────────────────────────────────

    [Fact]
    public void Rehydrate_SetsAllPropertiesWithoutValidation()
    {
        // Rehydrate can create entities that would fail Create (e.g., IsActive=false)
        var entity = ChargeableCharConfig.Rehydrate(
            id: 42, productTypeId: 5, symbol: "$", category: "Currency",
            price: 1.5m, validFrom: Today, validTo: Today.AddDays(30), isActive: false);

        entity.Id.Should().Be(42);
        entity.ProductTypeId.Should().Be(5);
        entity.Symbol.Should().Be("$");
        entity.Category.Should().Be("Currency");
        entity.PricePerUnit.Should().Be(1.5m);
        entity.ValidFrom.Should().Be(Today);
        entity.ValidTo.Should().Be(Today.AddDays(30));
        entity.IsActive.Should().BeFalse();
    }
}
