using Femto.Api.Sessions;
using Femto.Common;
using Femto.Modules.Auth.Application;
using Femto.Modules.Auth.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Femto.Api.Controllers.Auth;

[ApiController]
[Route("auth")]
public class AuthController(ICurrentUserContext currentUserContext, IAuthService authService)
    : ControllerBase
{
    [HttpPost("login")]
    public async Task<ActionResult<LoginResponse>> Login(
        [FromBody] LoginRequest request,
        CancellationToken cancellationToken
    )
    {
        var result = await authService.AuthenticateUserCredentials(
            request.Username,
            request.Password,
            cancellationToken
        );

        if (result is null)
            return this.BadRequest();

        var (user, session) = result;

        HttpContext.SetSession(session, user);

        if (request.RememberMe)
        {
            var newRememberMeToken = await authService.CreateRememberMeToken(user.Id);
            HttpContext.SetRememberMeToken(newRememberMeToken);
        }

        return new LoginResponse(user.Id, user.Username, user.Roles.Any(r => r == Role.SuperUser));
    }

    [HttpPost("register")]
    public async Task<ActionResult<RegisterResponse>> Register(
        [FromBody] RegisterRequest request,
        CancellationToken cancellationToken
    )
    {
        var (user, session) = await authService.CreateUserWithCredentials(
            request.Username,
            request.Password,
            request.SignupCode,
            cancellationToken
        );

        HttpContext.SetSession(session, user);

        if (request.RememberMe)
        {
            var newRememberMeToken = await authService.CreateRememberMeToken(user.Id);
            HttpContext.SetRememberMeToken(newRememberMeToken);
        }

        return new RegisterResponse(
            user.Id,
            user.Username,
            user.Roles.Any(r => r == Role.SuperUser)
        );
    }

    [HttpPost("change-password")]
    public async Task<ActionResult> ChangePassword(
        [FromBody] ChangePasswordRequestBody req,
        CancellationToken cancellationToken
    )
    {
        if (currentUserContext.CurrentUser is not {} user)
            return this.BadRequest();

        // superuser do what superuser want
        if (!user.IsSuperUser)
        {
            if (user.Id != req.UserId)
                return this.BadRequest();

            var session = await authService.GetSession(this.HttpContext.GetSessionId()!);

            // require strong authentication to change password
            // the user can re-enter their password
            if (session is null || !session.IsStronglyAuthenticated)
                return this.BadRequest();
        }

        await authService.ChangePassword(req.UserId, req.NewPassword, cancellationToken);

        // TODO would be better do handle this from inside the auth service. maybe just have it happen in a post-save event handler?
        await authService.InvalidateUserSessions(req.UserId, cancellationToken);

        return this.Ok(new {});
    }

    [HttpPost("delete-current-session")]
    public async Task<ActionResult> DeleteSessionV2()
    {
        var sessionId = HttpContext.GetSessionId();

        if (sessionId is not null)
        {
            await authService.DeleteSession(sessionId);
            HttpContext.DeleteSession();
        }

        var rememberMeToken = HttpContext.GetRememberMeToken();

        if (rememberMeToken is not null)
        {
            await authService.DeleteRememberMeToken(rememberMeToken);
            HttpContext.DeleteRememberMeToken();
        }

        return Ok(new { });
    }

    [Obsolete("use POST /auth/delete-current-session")]
    [HttpDelete("session")]
    public async Task<ActionResult> DeleteSession()
    {
        var sessionId = HttpContext.GetSessionId();

        if (sessionId is not null)
        {
            await authService.DeleteSession(sessionId);
            HttpContext.DeleteSession();
        }

        var rememberMeToken = HttpContext.GetRememberMeToken();

        if (rememberMeToken is not null)
        {
            await authService.DeleteRememberMeToken(rememberMeToken);
            HttpContext.DeleteRememberMeToken();
        }

        return Ok(new { });
    }

    [HttpGet("user/{userId}")]
    [Authorize]
    public async Task<ActionResult<GetUserInfoResult>> GetUserInfo(
        Guid userId,
        CancellationToken cancellationToken
    )
    {
        var currentUser = currentUserContext.CurrentUser;

        if (currentUser is null || currentUser.Id != userId)
            return this.BadRequest();

        var user = await authService.GetUserWithId(userId, cancellationToken);

        if (user is null)
            return this.BadRequest();

        return new GetUserInfoResult(
            user.Id,
            user.Username,
            user.Roles.Any(r => r == Role.SuperUser)
        );
    }

    [Obsolete("use POST /auth/create-signup-code")]
    [HttpPost("signup-codes")]
    [Authorize(Roles = "SuperUser")]
    public async Task<ActionResult> CreateSignupCode(
        [FromBody] CreateSignupCodeRequest request,
        CancellationToken cancellationToken
    )
    {
        await authService.AddSignupCode(request.Code, request.Name, cancellationToken);

        return Ok(new { });
    }

    [Obsolete("use GET /auth/list-signup-codes")]
    [HttpGet("signup-codes")]
    [Authorize(Roles = "SuperUser")]
    public async Task<ActionResult<ListSignupCodesResult>> ListSignupCodes(
        CancellationToken cancellationToken
    )
    {
        var codes = await authService.GetSignupCodes(cancellationToken);

        return new ListSignupCodesResult(
            codes.Select(c => new SignupCodeDto(
                c.Code,
                c.Email,
                c.Name,
                c.RedeemedByUserId,
                c.RedeemedByUsername,
                c.ExpiresOn
            ))
        );
    }

    [HttpPost("create-signup-code")]
    [Authorize(Roles = "SuperUser")]
    public async Task<ActionResult> CreateSignupCodeV2(
        [FromBody] CreateSignupCodeRequest request,
        CancellationToken cancellationToken
    )
    {
        await authService.AddSignupCode(request.Code, request.Name, cancellationToken);

        return Ok(new { });
    }

    [HttpGet("list-signup-codes")]
    [Authorize(Roles = "SuperUser")]
    public async Task<ActionResult<ListSignupCodesResult>> ListSignupCodesV2(
        CancellationToken cancellationToken
    )
    {
        var codes = await authService.GetSignupCodes(cancellationToken);

        return new ListSignupCodesResult(
            codes.Select(c => new SignupCodeDto(
                c.Code,
                c.Email,
                c.Name,
                c.RedeemedByUserId,
                c.RedeemedByUsername,
                c.ExpiresOn
            ))
        );
    }
}
