using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.Service.Sentinel;
using eSuite.API.Models;
using eSuite.API.security;
using eSuite.API.Utilities;
using eSuite.Core.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace eSuite.API.Controllers;

/// <summary>
/// This part of the API is responsible for creating and maintaining user sessions within e-suite
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ESuiteControllerBase
{
    private readonly IUserManager _userManager;
    private readonly ISentinel _sentinel;

    private const string AccessDeniedText = "Access denied";
    private const string LoginAcceptedText = "Login accepted";

    /// <summary>
    /// Default constructor used for dependency injection
    /// </summary>
    /// <param name="userManager"></param>
    /// <param name="sentinel"></param>
    public AuthenticationController(IUserManager userManager, ISentinel sentinel)
    {
        _userManager = userManager;
        _sentinel = sentinel;
    }

    /// <summary>
    /// Request an password recovery email is sent
    /// </summary>
    /// <remarks>A password recovery email allows someone to gain access to the system after they have lost their password.  It will allow them to reset their password.</remarks>
    /// <returns></returns>
    [Route("forgotPassword")]
    [HttpPost]
    [AccessKey(SecurityAccess.Everyone)]
    [AllowAnonymous]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> ForgotPassword(
        [FromBody] EmailAddress forgotPasswordRequest,
        CancellationToken cancellationToken = default!
    )
    {
        try
        {
            var securityResult = await _sentinel.CheckSecurity(this, cancellationToken);
            if (securityResult != null)
                return securityResult;

            await _userManager.ForgotPassword(forgotPasswordRequest.Email, cancellationToken);

            return Ok();
        }
        catch (NotFoundException)
        {
            await _sentinel.LogBadRequest(this, cancellationToken);
            throw;
        }
        catch (EmailNotConfirmedException)
        {
            await _sentinel.LogBadRequest(this, cancellationToken);
            return Unauthorized(new ProblemDetails
            {
                Title = AccessDeniedText,
                Detail = "Email not confirmed"
            });
        }
    }

    /// <summary>
    /// Login with username, password and optionally Two Factor Authentication code
    /// </summary>
    /// <remarks>Log into e-suite using username, password and optionally Two factor Authentication.   This call can also be used to attempt to request two factor authentication is disabled.</remarks>
    /// <example>This is my example</example>
    /// <returns></returns>
    /// <response code="200">Authentication successful. The "instance" property will contain the token to include to access secure api calls</response>
    /// <response code="202">Username and password accepted. Two factor (Tfa) security code required, or request to disable Tfa processed.  See the detail property.</response>
    /// <response code="401">Authentication is unsuccessful</response>
    [Route("login")]
    [HttpPost]
    [AccessKey(SecurityAccess.Everyone)]
    [AllowAnonymous]
    [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SuccessfulLogin))]
    [ProducesResponseType(StatusCodes.Status202Accepted)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [Produces("application/json")]
    [Obsolete("Login is dealt with via SSO now.   Only username and password auth will work with this call.  Will be replaced with user created API Keys.")]
    public async Task<IActionResult> Login([FromBody] Login login, CancellationToken cancellationToken = default!)
    {
        var securityResult = await _sentinel.CheckSecurity(this, cancellationToken);
        if (securityResult != null)
            return securityResult;

        var loginResponse = await _userManager.Login(login, cancellationToken);
        switch (loginResponse.Result)
        {
            case LoginResult.EmailNotConfirmed:
                return Unauthorized(new ProblemDetails
                {
                    Title = AccessDeniedText,
                    Detail = "Email not confirmed"
                });
            case LoginResult.TwoFactorAuthenticationRemovalRequested:
                return Accepted(new ProblemDetails
                {
                    Title = LoginAcceptedText,
                    Detail = "Request to remove two factor authentication accepted"
                });
            case LoginResult.TwoFactorAuthenticationCodeRequired:
                return Accepted(new ProblemDetails
                {
                    Title = LoginAcceptedText,
                    Detail = "Please supply Two Factor Authentication code"
                });
            case LoginResult.TwoFactorAuthenticationCodeIncorrect:
                await _sentinel.LogBadRequest(this, cancellationToken);
                return Unauthorized(new ProblemDetails
                {
                    Title = AccessDeniedText,
                    Detail = "Incorrect authorisation code"

                });
            case LoginResult.Success:
                return Ok(new SuccessfulLogin
                {
                    Title = LoginAcceptedText,
                    Token = loginResponse.Token
                });
            case LoginResult.Failed:
            default:
                await _sentinel.LogBadRequest(this, cancellationToken);
                return Unauthorized(new ProblemDetails
                {
                    Title = AccessDeniedText, Detail = "Email and/or Password has been entered incorrectly"
                });
        }
    }

    /// <summary>
    /// Call this to complete the action for an email with an action link.
    /// </summary>
    /// <remarks>There are several functions in the system that will result in an email being sent to the user with a link so they can confirm.  Currently these are to for a forgotten password, to disable the two factor authentication and for a new user to confirm their e-mail</remarks>
    /// <param name="token"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("completeEmailAction")]
    [HttpPost]
    [AccessKey(SecurityAccess.Everyone)]
    [AllowAnonymous]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))]
    public async Task<IActionResult> CompleteEmailAction(
        [FromBody] EmailActionToken token,
        CancellationToken cancellationToken = default!
    )
    {
        var securityResult = await _sentinel.CheckSecurity(this, cancellationToken);
        if (securityResult != null)
            return securityResult;

        try
        {
            await _userManager.CompleteEmailAction(token, cancellationToken);
            return Ok();
        }
        catch (TokenInvalidException)
        {
            await _sentinel.LogBadRequest(this, cancellationToken);
            throw;
        }
        catch (InvalidEmailException)
        {
            await _sentinel.LogBadRequest(this, cancellationToken);
            throw;
        }
    }

    /// <summary>
    /// Get a new token to replace your current token.
    /// </summary>
    /// <remarks>e-suite authentication tokens are valid for a limited amount of time.  To gain more time on your user session you will need to exchange your token for a new one.</remarks>
    /// <returns></returns>
    [Route("refreshToken")]
    [HttpGet]
    [AccessKey(SecurityAccess.Everyone)]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> RefreshToken(CancellationToken cancellationToken = default!)
    {
        var userId = User.GeneralIdRef();

        var loginResponse = await _userManager.RefreshToken(userId, cancellationToken);

        if (loginResponse.Result != LoginResult.Success)
            return Unauthorized(new ProblemDetails
            {
                Title = AccessDeniedText,
                Detail = "Username or Password incorrect"
            });

        return Ok(new SuccessfulLogin { Title = "Access Granted", Token = loginResponse.Token });
    }
}