using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Baya.Application.Contracts.Identity;
using Microsoft.Extensions.DependencyInjection;

namespace Baya.Test.Api;

/// <summary>
/// HTTP-pipeline coverage for the b15 ticket system. The load-bearing assertion is the <c>is_internal</c> hard
/// boundary: an admin internal note is stripped from the user thread view and present in the admin thread view.
/// Also covers participant uniqueness (a duplicate add is a 409, not a 500) and auth gates.
/// </summary>
public class MessagingApiTests(BayaApiFactory factory) : IClassFixture<BayaApiFactory>
{
    private static async Task<int> UserIdAsync(BayaApiFactory factory, string phone)
    {
        using var scope = factory.Services.CreateScope();
        var users = scope.ServiceProvider.GetRequiredService<IAppUserManager>();
        var user = await users.GetUserByPhoneNumber(phone);
        return user!.Id;
    }

    [Fact]
    public async Task Tickets_Unauthenticated_Returns401()
    {
        var anon = factory.CreateClient();
        var response = await anon.GetAsync("/api/v1/tickets");
        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }

    [Fact]
    public async Task InternalNote_IsHiddenInUserView_ShownInAdminView()
    {
        var user = factory.CreateClient();
        var login = await AuthTestClient.LoginAsync(factory, user, "09120010001");
        AuthTestClient.UseBearer(user, login.GetProperty("accessToken").GetString()!);

        // Open a pure support ticket (no booking/refund link) and post a normal message.
        var open = await user.PostAsJsonAsync("/api/v1/tickets", new { category = "support", subject = "Help", body = "My first message" });
        Assert.Equal(HttpStatusCode.OK, open.StatusCode);
        var opened = await AuthTestClient.ReadDataAsync(open);
        var ticketId = opened.GetProperty("ticketId").GetInt64();
        Assert.StartsWith("TKT-", opened.GetProperty("referenceCode").GetString());

        // Admin (staff) posts an internal note.
        var admin = factory.CreateClient();
        await AdminTestClient.AuthenticateAsync(factory, admin, "09120010009");
        var internalPost = await admin.PostAsJsonAsync($"/api/v1/tickets/{ticketId}/messages", new { body = "internal only", isInternal = true });
        Assert.Equal(HttpStatusCode.OK, internalPost.StatusCode);

        // User view — the internal note is absent (only the original message).
        var userThread = await AuthTestClient.ReadDataAsync(await user.GetAsync($"/api/v1/tickets/{ticketId}"));
        var userMessages = userThread.GetProperty("messages").EnumerateArray().ToList();
        Assert.Single(userMessages);
        Assert.All(userMessages, m => Assert.False(m.GetProperty("isInternal").GetBoolean()));

        // Admin view — the internal note IS present.
        var adminThread = await AuthTestClient.ReadDataAsync(await admin.GetAsync($"/api/v1/admin/tickets/{ticketId}"));
        var adminMessages = adminThread.GetProperty("messages").EnumerateArray().ToList();
        Assert.Equal(2, adminMessages.Count);
        Assert.Contains(adminMessages, m => m.GetProperty("isInternal").GetBoolean());
    }

    [Fact]
    public async Task NonAdmin_CannotSetInternal()
    {
        var user = factory.CreateClient();
        var login = await AuthTestClient.LoginAsync(factory, user, "09120020001");
        AuthTestClient.UseBearer(user, login.GetProperty("accessToken").GetString()!);

        var opened = await AuthTestClient.ReadDataAsync(
            await user.PostAsJsonAsync("/api/v1/tickets", new { category = "support", subject = "x", body = "hi" }));
        var ticketId = opened.GetProperty("ticketId").GetInt64();

        var response = await user.PostAsJsonAsync($"/api/v1/tickets/{ticketId}/messages", new { body = "sneaky", isInternal = true });
        Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
    }

    [Fact]
    public async Task AddParticipant_DuplicateIsConflict_NotServerError()
    {
        var user = factory.CreateClient();
        var login = await AuthTestClient.LoginAsync(factory, user, "09120030001");
        AuthTestClient.UseBearer(user, login.GetProperty("accessToken").GetString()!);

        var opened = await AuthTestClient.ReadDataAsync(
            await user.PostAsJsonAsync("/api/v1/tickets", new { category = "support", subject = "x", body = "hi" }));
        var ticketId = opened.GetProperty("ticketId").GetInt64();

        // A second user to attach.
        await AuthTestClient.CreateUserWithOtpCodeAsync(factory, "09120030002");
        var otherUserId = await UserIdAsync(factory, "09120030002");

        var admin = factory.CreateClient();
        await AdminTestClient.AuthenticateAsync(factory, admin, "09120030009");

        var first = await admin.PostAsJsonAsync($"/api/v1/tickets/{ticketId}/participants", new { userId = otherUserId });
        Assert.Equal(HttpStatusCode.OK, first.StatusCode);

        var duplicate = await admin.PostAsJsonAsync($"/api/v1/tickets/{ticketId}/participants", new { userId = otherUserId });
        Assert.Equal(HttpStatusCode.Conflict, duplicate.StatusCode);

        var removed = await admin.DeleteAsync($"/api/v1/tickets/{ticketId}/participants/{otherUserId}");
        Assert.Equal(HttpStatusCode.OK, removed.StatusCode);
    }
}
