using Dapper;
using FluentAssertions;
using SIGCM2.Domain.Entities;
using SIGCM2.Infrastructure.Persistence;
using SIGCM2.TestSupport;

namespace SIGCM2.Application.Tests.ProductTypes;

/// <summary>
/// Integration tests for ProductTypeRepository against SIGCM2_Test_App.
/// Uses shared SqlTestFixture via [Collection("Database")] — fixture maneja Respawn + seeds.
/// Temporal: after UpdateAsync, dbo.ProductType_History MUST have ≥1 row for that Id.
/// </summary>
[Collection("Database")]
public class ProductTypeRepositoryTests : IAsyncLifetime
{
    private readonly SqlTestFixture _db;
    private ProductTypeRepository _repository = null!;
    private TimeProvider _timeProvider = null!;

    public ProductTypeRepositoryTests(SqlTestFixture db)
    {
        _db = db;
    }

    public async Task InitializeAsync()
    {
        await _db.ResetAndSeedAsync();

        var factory = new SqlConnectionFactory(TestConnectionStrings.AppTestDb);
        _repository = new ProductTypeRepository(factory);
        _timeProvider = TimeProvider.System;
    }

    public Task DisposeAsync() => Task.CompletedTask;

    // ── AddAsync + GetByIdAsync roundtrip ─────────────────────────────────────

    [Fact]
    public async Task AddAsync_AndGetById_ReturnsAllFields()
    {
        var pt = ProductType.ForCreation(
            nombre: "Avisos Clasificados",
            hasDuration: true,
            requiresText: true,
            requiresCategory: true,
            isBundle: false,
            allowImages: true,
            maxImages: 5,
            maxImageSizeMB: 2.5m,
            maxImageWidth: 800,
            maxImageHeight: 600,
            timeProvider: _timeProvider);

        var id = await _repository.AddAsync(pt);
        var result = await _repository.GetByIdAsync(id);

        result.Should().NotBeNull();
        result!.Id.Should().Be(id);
        result.Nombre.Should().Be("Avisos Clasificados");
        result.HasDuration.Should().BeTrue();
        result.RequiresText.Should().BeTrue();
        result.RequiresCategory.Should().BeTrue();
        result.IsBundle.Should().BeFalse();
        result.AllowImages.Should().BeTrue();
        result.MaxImages.Should().Be(5);
        result.MaxImageSizeMB.Should().Be(2.5m);
        result.MaxImageWidth.Should().Be(800);
        result.MaxImageHeight.Should().Be(600);
        result.IsActive.Should().BeTrue();
        result.FechaCreacion.Should().BeAfter(DateTime.MinValue);
        result.FechaModificacion.Should().BeNull();
    }

    [Fact]
    public async Task AddAsync_NoImages_PersistsNullMultimedia()
    {
        var pt = ProductType.ForCreation(
            nombre: "Aviso Simple",
            hasDuration: false,
            requiresText: false,
            requiresCategory: false,
            isBundle: false,
            allowImages: false,
            maxImages: null,
            maxImageSizeMB: null,
            maxImageWidth: null,
            maxImageHeight: null,
            timeProvider: _timeProvider);

        var id = await _repository.AddAsync(pt);
        var result = await _repository.GetByIdAsync(id);

        result.Should().NotBeNull();
        result!.AllowImages.Should().BeFalse();
        result.MaxImages.Should().BeNull();
        result.MaxImageSizeMB.Should().BeNull();
        result.MaxImageWidth.Should().BeNull();
        result.MaxImageHeight.Should().BeNull();
    }

    [Fact]
    public async Task GetByIdAsync_NonExistent_ReturnsNull()
    {
        var result = await _repository.GetByIdAsync(999999);

        result.Should().BeNull();
    }

    // ── ExistsByNombreAsync ────────────────────────────────────────────────────

    [Fact]
    public async Task ExistsByNombreAsync_ExistingActiveNombre_ReturnsTrue()
    {
        var pt = ProductType.ForCreation("Tipo Unico", false, false, false, false, false, null, null, null, null, _timeProvider);
        await _repository.AddAsync(pt);

        var exists = await _repository.ExistsByNombreAsync("Tipo Unico");

        exists.Should().BeTrue();
    }

    [Fact]
    public async Task ExistsByNombreAsync_WithExcludeId_ExcludesSelf()
    {
        var pt = ProductType.ForCreation("Auto-Excluido", false, false, false, false, false, null, null, null, null, _timeProvider);
        var id = await _repository.AddAsync(pt);

        var exists = await _repository.ExistsByNombreAsync("Auto-Excluido", excludeId: id);

        exists.Should().BeFalse();
    }

    [Fact]
    public async Task ExistsByNombreAsync_InactiveNombre_ReturnsFalse()
    {
        var pt = ProductType.ForCreation("Tipo Inactivo", false, false, false, false, false, null, null, null, null, _timeProvider);
        var id = await _repository.AddAsync(pt);
        var entity = await _repository.GetByIdAsync(id);
        await _repository.UpdateAsync(entity!.WithDeactivated(_timeProvider));

        var exists = await _repository.ExistsByNombreAsync("Tipo Inactivo");

        exists.Should().BeFalse();
    }

    // ── UpdateAsync ────────────────────────────────────────────────────────────

    [Fact]
    public async Task UpdateAsync_PersistsChanges()
    {
        var pt = ProductType.ForCreation("Original", false, false, false, false, false, null, null, null, null, _timeProvider);
        var id = await _repository.AddAsync(pt);
        var loaded = await _repository.GetByIdAsync(id);

        var updated = loaded!
            .WithRenamed("Renombrado", _timeProvider)
            .WithUpdatedFlags(true, true, false, false, _timeProvider);
        await _repository.UpdateAsync(updated);

        var result = await _repository.GetByIdAsync(id);
        result!.Nombre.Should().Be("Renombrado");
        result.HasDuration.Should().BeTrue();
        result.RequiresText.Should().BeTrue();
        result.FechaModificacion.Should().NotBeNull();
    }

    [Fact]
    public async Task UpdateAsync_DeactivatesAndRecordsHistory()
    {
        var pt = ProductType.ForCreation("Para Baja", false, false, false, false, false, null, null, null, null, _timeProvider);
        var id = await _repository.AddAsync(pt);
        var loaded = await _repository.GetByIdAsync(id);

        await _repository.UpdateAsync(loaded!.WithDeactivated(_timeProvider));

        var result = await _repository.GetByIdAsync(id);
        result!.IsActive.Should().BeFalse();

        // Verify temporal history has at least 1 row
        var historyCount = await _db.Connection.ExecuteScalarAsync<int>(
            "SELECT COUNT(1) FROM dbo.ProductType_History WHERE Id = @Id", new { Id = id });
        historyCount.Should().BeGreaterThan(0);
    }

    // ── GetPagedAsync ──────────────────────────────────────────────────────────

    [Fact]
    public async Task GetPagedAsync_FiltersActiveByDefault()
    {
        var active = ProductType.ForCreation("Activo Paginado", false, false, false, false, false, null, null, null, null, _timeProvider);
        var inactive = ProductType.ForCreation("Inactivo Paginado", false, false, false, false, false, null, null, null, null, _timeProvider);

        await _repository.AddAsync(active);
        var inactiveId = await _repository.AddAsync(inactive);
        var inactiveLoaded = await _repository.GetByIdAsync(inactiveId);
        await _repository.UpdateAsync(inactiveLoaded!.WithDeactivated(_timeProvider));

        var query = new SIGCM2.Application.Common.ProductTypesQuery(Page: 1, PageSize: 50, Activo: true);
        var result = await _repository.GetPagedAsync(query);

        result.Items.Should().Contain(x => x.Nombre == "Activo Paginado");
        result.Items.Should().NotContain(x => x.Nombre == "Inactivo Paginado");
    }

    [Fact]
    public async Task GetPagedAsync_SearchFilter_FiltersCorrectly()
    {
        await _repository.AddAsync(ProductType.ForCreation("Buscar ABC", false, false, false, false, false, null, null, null, null, _timeProvider));
        await _repository.AddAsync(ProductType.ForCreation("Otro Tipo", false, false, false, false, false, null, null, null, null, _timeProvider));

        var query = new SIGCM2.Application.Common.ProductTypesQuery(Page: 1, PageSize: 50, Activo: null, Search: "ABC");
        var result = await _repository.GetPagedAsync(query);

        result.Items.Should().Contain(x => x.Nombre == "Buscar ABC");
        result.Items.Should().NotContain(x => x.Nombre == "Otro Tipo");
    }

    [Fact]
    public async Task GetPagedAsync_Pagination_RespectsPageSize()
    {
        for (var i = 1; i <= 5; i++)
            await _repository.AddAsync(ProductType.ForCreation($"Paginado {i:D2}", false, false, false, false, false, null, null, null, null, _timeProvider));

        var query = new SIGCM2.Application.Common.ProductTypesQuery(Page: 1, PageSize: 3, Activo: null);
        var result = await _repository.GetPagedAsync(query);

        result.Items.Should().HaveCount(3);
        result.Total.Should().BeGreaterThanOrEqualTo(5);
        result.Page.Should().Be(1);
        result.PageSize.Should().Be(3);
    }
}
