namespace FiscalOS.API.Tests.Integration.Auth;

public class LoginTests(TestApi testApi) : IntegrationTest(testApi)
{
  private static readonly Uri LoginUri = new("/auth/login", UriKind.Relative);

  [Theory]
  [ClassData<LoginValidationTestCases>]
  public async Task Login_WhenUserSubmitsInvalidRequest_ItShouldReturn400WithProblemDetails(LoginValidationTestCase tc)
  {
    using var request = HttpRequestBuilder.New()
      .Post(LoginUri)
      .WithBody(new
      {
        username = tc.Username,
        password = tc.Password,
      })
      .Build();

    var res = await Client.SendAsync(request, TestContext.Current.CancellationToken);

    await res.Should().BeValidationProblemDetails(tc.ExpectedErrors);
  }

  [Fact]
  public async Task Login_WhenUserDoesNotExist_ItShouldReturn401WithProblemDetails()
  {
    using var request = HttpRequestBuilder.New()
      .Post(LoginUri)
      .WithBody(new
      {
        username = "Test",
        password = "@Password2",
      })
      .Build();

    var res = await Client.SendAsync(request, TestContext.Current.CancellationToken);

    await res.Should().BeProblemDetails(HttpStatusCode.Unauthorized);
  }

  [Fact]
  public async Task Login_WhenUserExistsButPasswordIsIncorrect_ItShouldReturn401WithProblemDetails()
  {
    await Api.ExecuteAsync(static async (context, ct, sp) =>
    {
      var passwordHasher = sp.GetRequiredService<IPasswordHasher>();
      var encryptor = sp.GetRequiredService<IEncryptor>();

      var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct);
      context.Add(User.From("Stevan", passwordHasher.Hash("@Password1"), userEncryptionKey));

      await context.SaveChangesAsync(ct);
    }, TestContext.Current.CancellationToken);

    using var request = HttpRequestBuilder.New()
      .Post(LoginUri)
      .WithBody(new
      {
        username = "Stevan",
        password = "@Password2",
      })
      .Build();

    var res = await Client.SendAsync(request, TestContext.Current.CancellationToken);

    await res.Should().BeProblemDetails(HttpStatusCode.Unauthorized);
  }

  [Fact]
  public async Task Login_WhenUserExistsAndPasswordIsCorrect_ItShouldReturn200WithJwtTokenAndSetRefreshCookie()
  {
    await Api.ExecuteAsync(static async (context, ct, sp) =>
    {
      var passwordHasher = sp.GetRequiredService<IPasswordHasher>();
      var encryptor = sp.GetRequiredService<IEncryptor>();

      var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct);
      context.Add(User.From("Stevan", passwordHasher.Hash("@Password1"), userEncryptionKey));

      await context.SaveChangesAsync(ct);
    }, TestContext.Current.CancellationToken);

    using var request = HttpRequestBuilder.New()
      .Post(LoginUri)
      .WithBody(new
      {
        username = "Stevan",
        password = "@Password1",
      })
      .Build();

    var res = await Client.SendAsync(request, TestContext.Current.CancellationToken);

    res.Should().HaveSetCookieHeader("fiscalos_refresh_cookie");
    await res.Should().BeJsonContentOfType<API.Auth.Login.Response>(HttpStatusCode.OK);
  }
}


public class LoginValidationTestCases : TheoryData<LoginValidationTestCase>
{
  public LoginValidationTestCases()
  {
    Add(new LoginValidationTestCase(
      "No username or password",
      string.Empty,
      string.Empty,
      new SerializableDictionary<string, string[]>()
      {
        ["Username"] = ["The Username field is required."],
        ["Password"] = ["The Password field is required."]
      }
    ));

    Add(new LoginValidationTestCase(
      "No username",
      string.Empty,
      "@Password1",
      new SerializableDictionary<string, string[]>()
      {
        ["Username"] = ["The Username field is required."],
      }
    ));

    Add(new LoginValidationTestCase(
      "No password",
      "Stevan",
      string.Empty,
      new SerializableDictionary<string, string[]>()
      {
        ["Password"] = ["The Password field is required."]
      }
    ));
  }
}

public record LoginValidationTestCase : IXunitSerializable
{
  public string Name { get; private set; } = string.Empty;
  public string Username { get; private set; } = string.Empty;
  public string Password { get; private set; } = string.Empty;
  public SerializableDictionary<string, string[]> ExpectedErrors { get; private set; } = [];

  public override string ToString()
  {
    return Name;
  }

  public LoginValidationTestCase()
  {
  }

  public LoginValidationTestCase(
    string name,
    string username,
    string password,
    SerializableDictionary<string, string[]> expectedErrors
  )
  {
    Name = name;
    Username = username;
    Password = password;
    ExpectedErrors = expectedErrors;
  }

  public void Deserialize(IXunitSerializationInfo info)
  {
    Name = info.GetValue<string>(nameof(Name)) ?? string.Empty;
    Username = info.GetValue<string>(nameof(Username)) ?? string.Empty;
    Password = info.GetValue<string>(nameof(Password)) ?? string.Empty;
    ExpectedErrors = info.GetValue<SerializableDictionary<string, string[]>>(nameof(ExpectedErrors)) ?? [];
  }

  public void Serialize(IXunitSerializationInfo info)
  {
    info.AddValue(nameof(Name), Name);
    info.AddValue(nameof(Username), Username);
    info.AddValue(nameof(Password), Password);
    info.AddValue(nameof(ExpectedErrors), ExpectedErrors);
  }
}