using System.Net.Http.Json;
using System.Reflection;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Tiku.Api.Contracts;
using Tiku.Api.Controllers;
using Tiku.Domain.Identity;
using Tiku.Domain.Tenancy;

namespace Tiku.IntegrationTests.Api;

public sealed class AuthPasswordLifecycleTests
{
    [Fact]
    public async Task Phone_and_password_login_authenticates_without_totp()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedUserAsync(factory);
        using var client = factory.CreateClient();
        client.DefaultRequestHeaders.Add("x-tenant-code", seed.TenantId.ToString("N"));

        using var login = await PostPasswordLoginAsync(client, seed);

        Assert.Equal("authenticated", login.RootElement.GetProperty("status").GetString());
        Assert.Equal(seed.Phone, login.RootElement.GetProperty("user").GetProperty("phone").GetString());
        Assert.False(login.RootElement.TryGetProperty("challengeToken", out var challenge) &&
                     challenge.ValueKind == JsonValueKind.String);
    }

    [Fact]
    public async Task Forced_password_change_finishes_with_an_authenticated_session()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedUserAsync(factory, forcePasswordChange: true);
        using var client = factory.CreateClient();
        client.DefaultRequestHeaders.Add("x-tenant-code", seed.TenantId.ToString("N"));

        using var login = await PostPasswordLoginAsync(client, seed);
        Assert.Equal("password_change_required", login.RootElement.GetProperty("status").GetString());

        var response = await client.PostAsJsonAsync(
            "/api/auth/password/change-required",
            new RequiredPasswordChangeDto
            {
                ChallengeToken = login.RootElement.GetProperty("challengeToken").GetString()!,
                NewPassword = "ChangedPassword2026"
            });
        response.EnsureSuccessStatusCode();
        using var changed = JsonDocument.Parse(await response.Content.ReadAsStringAsync());

        Assert.Equal("authenticated", changed.RootElement.GetProperty("status").GetString());
        Assert.False(changed.RootElement.TryGetProperty("challengeToken", out var challenge) &&
                     challenge.ValueKind == JsonValueKind.String);
    }

    [Theory]
    [InlineData(nameof(AuthController.LoginWithPassword), "login/password")]
    [InlineData(nameof(AuthController.SendSmsCode), "sms/send")]
    [InlineData(nameof(AuthController.LoginWithSms), "login/sms")]
    [InlineData(nameof(AuthController.LoginWithWechatWeb), "oauth/wechat")]
    [InlineData(nameof(AuthController.LoginWithWechatMiniApp), "oauth/wechat-miniapp")]
    [InlineData(nameof(AuthController.ChangeRequiredPassword), "password/change-required")]
    [InlineData(nameof(AuthController.Refresh), "refresh")]
    [InlineData(nameof(AuthController.Logout), "logout")]
    [InlineData(nameof(AuthController.LogoutAll), "logout-all")]
    public void Authentication_routes_match_the_v2_contract(string actionName, string route)
    {
        var action = typeof(AuthController).GetMethod(actionName, BindingFlags.Public | BindingFlags.Instance);
        var attribute = action?.GetCustomAttribute<HttpPostAttribute>();

        Assert.NotNull(attribute);
        Assert.Equal(route, attribute.Template);
    }

    private static async Task<JsonDocument> PostPasswordLoginAsync(
        HttpClient client,
        (Guid TenantId, string Phone) seed)
    {
        var response = await client.PostAsJsonAsync(
            "/api/auth/login/password",
            new PasswordLoginDto
            {
                Realm = AuthRealm.Tenant,
                TenantCode = seed.TenantId.ToString("N"),
                Identifier = seed.Phone,
                Password = PasswordTestUserExtensions.TestPassword
            });
        response.EnsureSuccessStatusCode();
        return JsonDocument.Parse(await response.Content.ReadAsStringAsync());
    }

    private static async Task<(Guid TenantId, string Phone)> SeedUserAsync(
        ApiTestFactory factory,
        bool forcePasswordChange = false)
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        const string phone = "13800000000";
        await factory.SeedAsync(
            new Tenant
            {
                Id = tenantId,
                Slug = tenantId.ToString("N"),
                Name = "Password Lifecycle Tenant"
            },
            new User
            {
                Id = userId,
                Phone = phone,
                Name = "Password Lifecycle User",
                ForcePasswordChange = forcePasswordChange
            }.WithTestPassword(),
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = userId,
                Role = TenantRole.TenantAdmin,
                Status = MembershipStatus.Active
            });
        return (tenantId, phone);
    }
}
