using Eis.Application.Authentication;
using Eis.Infrastructure.Authentication;

namespace Eis.Web.Authentication;

public static class NativeAuthenticationEndpoints
{
    private const string SessionCookieName = "hz_session";

    public static IEndpointRouteBuilder MapNativeAuthenticationEndpoints(
        this IEndpointRouteBuilder endpoints,
        AuthenticationOptions options)
    {
        if (!options.NativeEnabled)
        {
            return endpoints;
        }

        endpoints.MapPost("/api/auth/register", async (
            HttpContext context,
            RegisterRequest request,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.RegisterAsync(
                    request.Name ?? string.Empty,
                    request.Gender ?? string.Empty,
                    request.Password ?? string.Empty,
                    request.SchoolId ?? string.Empty,
                    request.ClassId ?? string.Empty,
                    cancellationToken)));

        endpoints.MapGet("/api/auth/me", async (
            HttpContext context,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.GetCurrentUserAsync(SessionToken(context), cancellationToken)));

        endpoints.MapPost("/api/auth/login", async (
            HttpContext context,
            LoginRequest request,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.LoginAsync(request.Username ?? string.Empty, request.Password ?? string.Empty, cancellationToken)));

        endpoints.MapPost("/api/auth/login/totp", async (
            HttpContext context,
            TotpLoginRequest request,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.CompleteTotpLoginAsync(
                    request.Challenge ?? string.Empty,
                    request.Code ?? string.Empty,
                    cancellationToken)));

        endpoints.MapPost("/api/auth/change-password", async (
            HttpContext context,
            ChangePasswordRequest request,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.ChangePasswordAsync(
                    SessionToken(context),
                    request.CurrentPassword ?? string.Empty,
                    request.NewPassword ?? string.Empty,
                    cancellationToken)));

        endpoints.MapGet("/api/auth/totp", async (
            HttpContext context,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.GetTotpStatusAsync(SessionToken(context), cancellationToken)));

        endpoints.MapPost("/api/auth/totp/setup", async (
            HttpContext context,
            PasswordRequest request,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.BeginTotpSetupAsync(
                    SessionToken(context),
                    request.CurrentPassword ?? string.Empty,
                    cancellationToken)));

        endpoints.MapPost("/api/auth/totp/enable", async (
            HttpContext context,
            CodeRequest request,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.EnableTotpAsync(
                    SessionToken(context),
                    request.Code ?? string.Empty,
                    cancellationToken)));

        endpoints.MapPost("/api/auth/totp/recovery-codes", async (
            HttpContext context,
            PasswordAndCodeRequest request,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.RegenerateRecoveryCodesAsync(
                    SessionToken(context),
                    request.CurrentPassword ?? string.Empty,
                    request.Code ?? string.Empty,
                    cancellationToken)));

        endpoints.MapPost("/api/auth/totp/disable", async (
            HttpContext context,
            PasswordAndCodeRequest request,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.DisableTotpAsync(
                    SessionToken(context),
                    request.CurrentPassword ?? string.Empty,
                    request.Code ?? string.Empty,
                    cancellationToken)));

        endpoints.MapPost("/api/auth/logout", async (
            HttpContext context,
            IAuthenticationService service,
            CancellationToken cancellationToken) => ToResult(
                context,
                await service.LogoutAsync(SessionToken(context), cancellationToken)));

        return endpoints;
    }

    private static string SessionToken(HttpContext context) =>
        context.Request.Cookies.TryGetValue(SessionCookieName, out var token) ? token : string.Empty;

    private static IResult ToResult(HttpContext context, AuthenticationEndpointResult result)
    {
        context.Response.Headers.CacheControl = "no-store";
        context.Response.Headers["X-EIS-Implementation"] = "aspnet-core";
        if (result.SetCookie is not null)
        {
            context.Response.Headers.Append("Set-Cookie", result.SetCookie);
        }

        return Results.Json(result.Body, statusCode: result.StatusCode);
    }

    private sealed class LoginRequest
    {
        public string? Username { get; init; }

        public string? Password { get; init; }
    }

    private sealed class RegisterRequest
    {
        public string? Name { get; init; }

        public string? Gender { get; init; }

        public string? Password { get; init; }

        public string? SchoolId { get; init; }

        public string? ClassId { get; init; }
    }

    private sealed class TotpLoginRequest
    {
        public string? Challenge { get; init; }

        public string? Code { get; init; }
    }

    private sealed class ChangePasswordRequest
    {
        public string? CurrentPassword { get; init; }

        public string? NewPassword { get; init; }
    }

    private sealed class PasswordRequest
    {
        public string? CurrentPassword { get; init; }
    }

    private sealed class CodeRequest
    {
        public string? Code { get; init; }
    }

    private sealed class PasswordAndCodeRequest
    {
        public string? CurrentPassword { get; init; }

        public string? Code { get; init; }
    }
}
