using System.Net;
using Newtonsoft.Json;

namespace AutobusApi.IntegrationTests.Tests;

public class IdentityTests : TestsBase
{
    public IdentityTests(CustomWebApplicationFactory<Program> factory)
        : base(factory) {}

    [Theory]
    [InlineData("valid@email.xyz", "12qw!@QW")]
    [InlineData("address@gmail.com", "123qwe!@#QWE")]
    public async Task Register_ValidCredentials_Returns200Ok(string email, string password)
    {
        var credentials = new
        {
            Email = email,
            Password = password
        };

        var response = await _httpClient.PostAsJsonAsync("identity/register", credentials);

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }

    [Theory]
    [InlineData("email.xyz", "12qw!@QW")]
    [InlineData("invalid.email.xyz", "12qw!@QW")]
    [InlineData("invalid@email", "12qw!@QW")]
    [InlineData("invalid@email.", "12qw!@QW")]
    [InlineData("invalid@email.c", "12qw!@QW")]
    [InlineData("@email.xyz", "12qw!@QW")]
    public async Task Register_InvalidEmail_Returns400BadRequest(string email, string password)
    {
        var credentials = new
        {
            Email = email,
            Password = password
        };

        var response = await _httpClient.PostAsJsonAsync("identity/register", credentials);

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

    [Theory]
    [InlineData("address@email.xyz", "1q!Q")] // Length is less than minimum (8)
    [InlineData("address@email.xyz", "12qw!@QW12qw!@QW12qw!@QW12qw!@QW12qw!@QW12qw!@QW12qw!@QW12qw!@QW_")] // Length is greater than maximum (64)
    [InlineData("address@email.xyz", "123456Qq")] // No special characters
    [InlineData("address@email.xyz", "123456q#")] // No uppercase letters characters
    [InlineData("address@email.xyz", "123456Q#")] // No lowercase letters characters
    public async Task Register_InvalidPassword_Returns400BadRequest(string email, string password)
    {
        var credentials = new
        {
            Email = email,
            Password = password
        };

        var response = await _httpClient.PostAsJsonAsync("identity/register", credentials);

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

    [Theory]
    [InlineData("some.address@gmail.com", "Vw35Vpn*A&lzX&)(ghAEX9\"@/Xt\"ip+0")]
    [InlineData("mail@mydomain.xyz", "Pa$$w0rD")]
    public async Task RegisterAndLogin_ValidCredentials_Returns200OK(string email, string password)
    {
        var credentials = new
        {
            Email = email,
            Password = password
        };

        var registrationResponse = await _httpClient.PostAsJsonAsync("identity/register", credentials);
        Assert.Equal(HttpStatusCode.OK, registrationResponse.StatusCode);

        var loginResponse = await _httpClient.PostAsJsonAsync("identity/login", credentials);
        Assert.Equal(HttpStatusCode.OK, loginResponse.StatusCode);

        var loginResponseContent = JsonConvert.DeserializeObject<dynamic>(await loginResponse.Content.ReadAsStringAsync());
        Assert.NotNull(loginResponseContent);
        Assert.NotNull(loginResponseContent!.accessToken);
        Assert.NotEmpty((string) loginResponseContent!.accessToken);
        Assert.NotNull(loginResponseContent!.refreshToken);
        Assert.NotEmpty((string) loginResponseContent!.refreshToken);
    }

    [Theory]
    [InlineData("not.registered@email.xyz", "12qw!@QW")]
    public async Task Login_InvalidCredentials_Returns400BadRequest(string email, string password)
    {
        var credentials = new
        {
            Email = email,
            Password = password
        };

        var response = await _httpClient.PostAsJsonAsync("identity/login", credentials);

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

    [Theory]
    [InlineData("some.address@gmail.com", "Vw35Vpn*A&lzX&)(ghAEX9\"@/Xt\"ip+0", 10)]
    public async Task RegisterThenLoginThenRenewAccessToken_ValidCredentials_Returns200OK(string email, string password, int renewCout)
    {
        var credentials = new
        {
            Email = email,
            Password = password
        };

        var registrationResponse = await _httpClient.PostAsJsonAsync("identity/register", credentials);
        Assert.Equal(HttpStatusCode.OK, registrationResponse.StatusCode);

        var loginResponse = await _httpClient.PostAsJsonAsync("identity/login", credentials);
        Assert.Equal(HttpStatusCode.OK, loginResponse.StatusCode);

        var loginResponseContent = JsonConvert.DeserializeObject<dynamic>(await loginResponse.Content.ReadAsStringAsync());
        Assert.NotNull(loginResponseContent);
        Assert.NotNull(loginResponseContent!.accessToken);
        Assert.NotEmpty((string) loginResponseContent!.accessToken);
        Assert.NotNull(loginResponseContent!.refreshToken);
        Assert.NotEmpty((string) loginResponseContent!.refreshToken);

        for (int i = 0; i < renewCout; i++)
        {
            var renewAccessTokenRequestBody = new { refreshToken = (string) loginResponseContent!.refreshToken };
            var renewAccessTokenResponse = await _httpClient.PostAsJsonAsync("identity/renewAccessToken", renewAccessTokenRequestBody);
            Assert.Equal(HttpStatusCode.OK, renewAccessTokenResponse.StatusCode);

            var renewAccessTokenResponseContent =
                JsonConvert.DeserializeObject<dynamic>(await renewAccessTokenResponse.Content.ReadAsStringAsync());
            Assert.NotNull(renewAccessTokenResponseContent);
            Assert.NotNull(renewAccessTokenResponseContent!.accessToken);
            Assert.NotEmpty((string) renewAccessTokenResponseContent!.accessToken);
            Assert.NotNull(renewAccessTokenResponseContent!.refreshToken);
            Assert.NotEmpty((string) renewAccessTokenResponseContent!.refreshToken);
        }
    }

    [Theory]
    [InlineData("some.address@gmail.com", "Vw35Vpn*A&lzX&)(ghAEX9\"@/Xt\"ip+0")]
    public async Task RegisterThenLoginThenRevokeRefreshToken_ValidCredentials_Returns200OK(string email, string password)
    {
        var credentials = new
        {
            Email = email,
            Password = password
        };

        var registrationResponse = await _httpClient.PostAsJsonAsync("identity/register", credentials);
        Assert.Equal(HttpStatusCode.OK, registrationResponse.StatusCode);

        var loginResponse = await _httpClient.PostAsJsonAsync("identity/login", credentials);
        Assert.Equal(HttpStatusCode.OK, loginResponse.StatusCode);

        var loginResponseContent = JsonConvert.DeserializeObject<dynamic>(await loginResponse.Content.ReadAsStringAsync());
        Assert.NotNull(loginResponseContent);
        Assert.NotNull(loginResponseContent!.accessToken);
        Assert.NotEmpty((string) loginResponseContent!.accessToken);
        Assert.NotNull(loginResponseContent!.refreshToken);
        Assert.NotEmpty((string) loginResponseContent!.refreshToken);

        var revokeRefreshTokenRequestBody = new { refreshToken = (string) loginResponseContent!.refreshToken };
        var revokeRefreshTokenResponse = await _httpClient.PostAsJsonAsync("identity/revokeRefreshToken", revokeRefreshTokenRequestBody);
        Assert.Equal(HttpStatusCode.OK, revokeRefreshTokenResponse.StatusCode);
    }

    [Theory]
    [InlineData("some.address@gmail.com", "Vw35Vpn*A&lzX&)(ghAEX9\"@/Xt\"ip+0")]
    public async Task RegisterThenLoginThenRevokeRefreshTokenTwice_ValidCredentials_Returns400BadRequest(string email, string password)
    {
        var credentials = new
        {
            Email = email,
            Password = password
        };

        var registrationResponse = await _httpClient.PostAsJsonAsync("identity/register", credentials);
        Assert.Equal(HttpStatusCode.OK, registrationResponse.StatusCode);

        var loginResponse = await _httpClient.PostAsJsonAsync("identity/login", credentials);
        Assert.Equal(HttpStatusCode.OK, loginResponse.StatusCode);

        var loginResponseContent = JsonConvert.DeserializeObject<dynamic>(await loginResponse.Content.ReadAsStringAsync());
        Assert.NotNull(loginResponseContent);
        Assert.NotNull(loginResponseContent!.accessToken);
        Assert.NotEmpty((string) loginResponseContent!.accessToken);
        Assert.NotNull(loginResponseContent!.refreshToken);
        Assert.NotEmpty((string) loginResponseContent!.refreshToken);

        var revokeRefreshTokenRequestBody = new { refreshToken = (string) loginResponseContent!.refreshToken };
        var revokeRefreshTokenResponse = await _httpClient.PostAsJsonAsync("identity/revokeRefreshToken", revokeRefreshTokenRequestBody);
        Assert.Equal(HttpStatusCode.OK, revokeRefreshTokenResponse.StatusCode);

        revokeRefreshTokenResponse = await _httpClient.PostAsJsonAsync("identity/revokeRefreshToken", revokeRefreshTokenRequestBody);
        Assert.Equal(HttpStatusCode.BadRequest, revokeRefreshTokenResponse.StatusCode);
    }
}