using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using SIGCM2.TestSupport;

namespace SIGCM2.Api.Tests.ProductTypes;

/// <summary>
/// PRD-001 — Integration tests for /api/v1/product-types and /api/v1/admin/product-types.
/// Read endpoints require authentication (any role).
/// Write endpoints require permission 'catalogo:tipos:gestionar'.
/// Verifies HTTP status codes, response shapes, and ExceptionFilter mappings.
/// </summary>
[Collection("ApiIntegration")]
public sealed class ProductTypesControllerTests : IAsyncLifetime
{
    private const string ReadEndpoint = "/api/v1/product-types";
    private const string AdminEndpoint = "/api/v1/admin/product-types";
    private const string AdminUsername = "admin";
    private const string AdminPassword = "@Diego550@";

    private readonly HttpClient _client;

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

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

    // ── 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 HttpRequestMessage BuildRequest(HttpMethod method, string url, object? body = null, string? bearerToken = null)
    {
        var request = new HttpRequestMessage(method, url);
        if (bearerToken is not null)
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
        if (body is not null)
            request.Content = JsonContent.Create(body);
        return request;
    }

    // ── 401 guards on READ endpoints ───────────────────────────────────────────

    [Fact]
    public async Task List_WithoutAuth_Returns401()
    {
        using var req = BuildRequest(HttpMethod.Get, ReadEndpoint);
        var resp = await _client.SendAsync(req);
        Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
    }

    [Fact]
    public async Task GetById_WithoutAuth_Returns401()
    {
        using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/999999");
        var resp = await _client.SendAsync(req);
        Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
    }

    // ── 401 / 403 guards on WRITE endpoints ───────────────────────────────────

    [Fact]
    public async Task Create_WithoutAuth_Returns401()
    {
        using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "Test" });
        var resp = await _client.SendAsync(req);
        Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
    }

    // ── POST /api/v1/admin/product-types ──────────────────────────────────────

    [Fact]
    public async Task Create_WithAdmin_Returns201WithId()
    {
        var token = await GetAdminTokenAsync();
        var uniqueName = $"Tipo_Create_{Guid.NewGuid():N}";

        using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new
        {
            nombre = uniqueName,
            hasDuration = true,
            requiresText = false,
            requiresCategory = false,
            isBundle = false,
            allowImages = true,
            maxImages = 3,
            maxImageSizeMB = 1.5,
            maxImageWidth = (int?)null,
            maxImageHeight = (int?)null
        }, token);

        var resp = await _client.SendAsync(req);

        Assert.Equal(HttpStatusCode.Created, resp.StatusCode);
        var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
        Assert.True(json.GetProperty("id").GetInt32() > 0);
        Assert.Equal(uniqueName, json.GetProperty("nombre").GetString());
        Assert.True(json.GetProperty("isActive").GetBoolean());
    }

    [Fact]
    public async Task Create_DuplicateNombre_Returns409()
    {
        var token = await GetAdminTokenAsync();
        var uniqueName = $"DupNombre_{Guid.NewGuid():N}";

        using var req1 = BuildRequest(HttpMethod.Post, AdminEndpoint, new
        {
            nombre = uniqueName,
            hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
            allowImages = false
        }, token);
        var resp1 = await _client.SendAsync(req1);
        Assert.Equal(HttpStatusCode.Created, resp1.StatusCode);

        using var req2 = BuildRequest(HttpMethod.Post, AdminEndpoint, new
        {
            nombre = uniqueName,
            hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
            allowImages = false
        }, token);
        var resp2 = await _client.SendAsync(req2);

        Assert.Equal(HttpStatusCode.Conflict, resp2.StatusCode);
    }

    [Fact]
    public async Task Create_InvalidBody_Returns400()
    {
        var token = await GetAdminTokenAsync();

        using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new
        {
            nombre = string.Empty,   // invalid
            hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
            allowImages = false
        }, token);

        var resp = await _client.SendAsync(req);

        Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
    }

    // ── GET /api/v1/product-types ─────────────────────────────────────────────

    [Fact]
    public async Task List_WithAdmin_Returns200WithPaginatedResult()
    {
        var token = await GetAdminTokenAsync();

        using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}?page=1&pageSize=10", bearerToken: token);
        var resp = await _client.SendAsync(req);

        Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
        var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
        Assert.True(json.TryGetProperty("items", out _));
        Assert.True(json.TryGetProperty("total", out _));
    }

    // ── GET /api/v1/product-types/{id} ────────────────────────────────────────

    [Fact]
    public async Task GetById_NotFound_Returns404()
    {
        var token = await GetAdminTokenAsync();
        using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/999999999", bearerToken: token);
        var resp = await _client.SendAsync(req);
        Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
    }

    [Fact]
    public async Task GetById_ExistingId_Returns200()
    {
        var token = await GetAdminTokenAsync();
        var uniqueName = $"Tipo_GetById_{Guid.NewGuid():N}";

        using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new
        {
            nombre = uniqueName,
            hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
            allowImages = false
        }, token);
        var createResp = await _client.SendAsync(createReq);
        Assert.Equal(HttpStatusCode.Created, createResp.StatusCode);
        var created = await createResp.Content.ReadFromJsonAsync<JsonElement>();
        var id = created.GetProperty("id").GetInt32();

        using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/{id}", bearerToken: token);
        var resp = await _client.SendAsync(req);

        Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
        var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
        Assert.Equal(id, json.GetProperty("id").GetInt32());
        Assert.Equal(uniqueName, json.GetProperty("nombre").GetString());
    }

    // ── PUT /api/v1/admin/product-types/{id} ──────────────────────────────────

    [Fact]
    public async Task Update_NotFound_Returns404()
    {
        var token = await GetAdminTokenAsync();
        using var req = BuildRequest(HttpMethod.Put, $"{AdminEndpoint}/999999999", new
        {
            nombre = "No Existe",
            hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
            allowImages = false
        }, token);
        var resp = await _client.SendAsync(req);
        Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
    }

    // ── DELETE /api/v1/admin/product-types/{id} ───────────────────────────────

    // TODO PRD-002: agregar e2e test para DELETE → 409 cuando IProductQueryRepository.IsInUseAsync retorna true.
    // Bloqueado por W1 (RSA singleton sealed en TestWebApplicationFactory). Ref issue #36.
    // Coverage compositional actual: DeactivateProductTypeCommandHandlerTests (unit) + ExceptionFilterTests (unit → 409).

    [Fact]
    public async Task Deactivate_NotFound_Returns404()
    {
        var token = await GetAdminTokenAsync();
        using var req = BuildRequest(HttpMethod.Delete, $"{AdminEndpoint}/999999999", bearerToken: token);
        var resp = await _client.SendAsync(req);
        Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
    }

    [Fact]
    public async Task Deactivate_ExistingActive_Returns204()
    {
        var token = await GetAdminTokenAsync();
        var uniqueName = $"Tipo_Deactivate_{Guid.NewGuid():N}";

        using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new
        {
            nombre = uniqueName,
            hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
            allowImages = false
        }, token);
        var createResp = await _client.SendAsync(createReq);
        Assert.Equal(HttpStatusCode.Created, createResp.StatusCode);
        var created = await createResp.Content.ReadFromJsonAsync<JsonElement>();
        var id = created.GetProperty("id").GetInt32();

        using var req = BuildRequest(HttpMethod.Delete, $"{AdminEndpoint}/{id}", bearerToken: token);
        var resp = await _client.SendAsync(req);

        Assert.Equal(HttpStatusCode.NoContent, resp.StatusCode);
    }
}
