using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging.Abstractions;
using SIGCM2.Api.Filters;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Domain.Exceptions;
using SIGCM2.TestSupport;

namespace SIGCM2.Api.Tests.Rubros;

/// <summary>
/// Issue #41 — DeactivateRubroCommandHandler guard contra Products activos.
///
/// Unit tests: ExceptionFilter mapping for RubroConProductosActivosException → 409.
/// E2E: DELETE /api/v1/admin/rubros/{id} with stub IProductQueryRepository returning count > 0 → 409.
/// </summary>
[Collection("ApiIntegration")]
public sealed class RubrosDeactivateGuardTests : IAsyncLifetime
{
    private const string AdminEndpoint = "/api/v1/admin/rubros";
    private const string AdminUsername = "admin";
    private const string AdminPassword = "@Diego550@";

    private readonly TestWebAppFactory _factory;
    private readonly HttpClient _client;

    public RubrosDeactivateGuardTests(TestWebAppFactory factory)
    {
        _factory = factory;
        _client = factory.CreateClient();
    }

    public Task InitializeAsync() => Task.CompletedTask;
    public Task DisposeAsync() => Task.CompletedTask;

    // ── ExceptionFilter unit test (no DB, no HTTP) ────────────────────────────

    [Fact]
    public void ExceptionFilter_MapsRubroConProductosActivos_To409()
    {
        var filter = new ExceptionFilter(NullLogger<ExceptionFilter>.Instance);
        var httpContext = new Microsoft.AspNetCore.Http.DefaultHttpContext();
        var routeData = new Microsoft.AspNetCore.Routing.RouteData();
        var actionDescriptor = new Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor();
        var modelState = new Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary();
        var actionContext = new ActionContext(httpContext, routeData, actionDescriptor, modelState);
        var ctx = new ExceptionContext(actionContext, new List<IFilterMetadata>())
        {
            Exception = new RubroConProductosActivosException(rubroId: 5, productosActivosCount: 2)
        };

        filter.OnException(ctx);

        var result = Assert.IsType<ObjectResult>(ctx.Result);
        Assert.Equal(StatusCodes.Status409Conflict, result.StatusCode);
        var json = System.Text.Json.JsonSerializer.Serialize(result.Value);
        Assert.Contains("rubro_con_productos_activos", json);
    }

    // ── E2E: DELETE with stub reporting active products → 409 ─────────────────

    /// <summary>
    /// Verifies the 409 guard path end-to-end via a per-test DI override: injects a stub
    /// IProductQueryRepository that reports the Rubro has 2 active products,
    /// so no real Product row needs to exist in the database (issue #36 DI override pattern).
    /// </summary>
    [Fact]
    public async Task DeactivateRubro_WhenHasActiveProducts_Returns409WithErrorCode()
    {
        // Arrange: get admin token from shared client (same DB)
        var token = await GetAdminTokenAsync();

        // Create a real Rubro to have a valid id to deactivate
        using var createReq = new HttpRequestMessage(HttpMethod.Post, AdminEndpoint);
        createReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        createReq.Content = JsonContent.Create(new
        {
            nombre = $"Rubro_Guard41_{Guid.NewGuid():N}"[..30],
            parentId = (int?)null,
        });
        var createResp = await _client.SendAsync(createReq);
        Assert.Equal(HttpStatusCode.Created, createResp.StatusCode);
        var created = await createResp.Content.ReadFromJsonAsync<JsonElement>();
        var rubroId = created.GetProperty("id").GetInt32();

        try
        {
            // Override IProductQueryRepository to report 2 active products for any rubroId
            using var overrideClient = _factory.CreateClientWithOverrides(services =>
            {
                services.RemoveAll<IProductQueryRepository>();
                services.AddScoped<IProductQueryRepository>(_ => new StubProductQueryRepository(activeCount: 2));
            });

            var deleteReq = new HttpRequestMessage(HttpMethod.Delete, $"{AdminEndpoint}/{rubroId}");
            deleteReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
            var resp = await overrideClient.SendAsync(deleteReq);

            Assert.Equal(HttpStatusCode.Conflict, resp.StatusCode);
            var body = await resp.Content.ReadFromJsonAsync<JsonElement>();
            Assert.Equal("rubro_con_productos_activos", body.GetProperty("error").GetString());
        }
        finally
        {
            await DeleteRubroDirectAsync(rubroId);
        }
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private async Task<string> GetAdminTokenAsync()
    {
        var response = await _client.PostAsJsonAsync("/api/v1/auth/login", new
        {
            username = AdminUsername,
            password = AdminPassword
        });
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadFromJsonAsync<JsonElement>();
        return json.GetProperty("accessToken").GetString()!;
    }

    private static async Task DeleteRubroDirectAsync(int id)
    {
        await using var conn = new Microsoft.Data.SqlClient.SqlConnection(TestConnectionStrings.ApiTestDb);
        await conn.OpenAsync();
        await Dapper.SqlMapper.ExecuteAsync(conn, "ALTER TABLE dbo.Rubro SET (SYSTEM_VERSIONING = OFF)");
        await Dapper.SqlMapper.ExecuteAsync(conn, "DELETE FROM dbo.Rubro_History WHERE Id = @Id", new { Id = id });
        await Dapper.SqlMapper.ExecuteAsync(conn, "DELETE FROM dbo.Rubro WHERE Id = @Id", new { Id = id });
        await Dapper.SqlMapper.ExecuteAsync(conn,
            "ALTER TABLE dbo.Rubro SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Rubro_History, HISTORY_RETENTION_PERIOD = 10 YEARS))");
    }
}

/// <summary>
/// Stub: always reports a fixed count of active products for any rubroId.
/// Used by RubrosDeactivateGuardTests to verify the 409 guard without seeding real Product rows.
/// </summary>
file sealed class StubProductQueryRepository : IProductQueryRepository
{
    private readonly int _activeCount;

    public StubProductQueryRepository(int activeCount)
    {
        _activeCount = activeCount;
    }

    public Task<bool> ExistsActiveByProductTypeAsync(int productTypeId, CancellationToken ct = default)
        => Task.FromResult(false);

    public Task<int> CountActiveByRubroAsync(int rubroId, CancellationToken ct = default)
        => Task.FromResult(_activeCount);
}
