using System.Text.Json;
using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.Database.Audit;
using e_suite.Database.Core.Extensions;
using e_suite.Service.Sentinel;
using eSuite.API.Extensions;
using eSuite.API.security;
using eSuite.API.SingleSignOn;
using eSuite.API.Translation;
using eSuite.API.Utilities;
using eSuite.Core.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace eSuite.API.Controllers;

public class ProfileViewModel
{
    public Models.UserProfile Profile { get; set; }
    public ITranslatorFactory TranslatorFactory { get; set; }
}

public class Redirect
{
    public string RedirectTo { get; set; }
}

/// <summary>
/// This MVC controller is used to support all user interactions when logging in and out of the system, including profile updates.
/// </summary>
[Route("/[controller]")]
[ApiExplorerSettings(IgnoreApi = true)]
public class AccountController : ESuiteController
{
    private readonly IUserManager _userManager;
    private readonly ISentinel _sentinel;
    private readonly ISingleSignOn _singleSignOn;
    private readonly ICookieManager _cookieManager;
    private readonly ITranslatorFactory _translatorFactory;
    /// <summary>
    /// Default constructor used to inject dependencies
    /// </summary>
    /// <param name="userManager"></param>
    /// <param name="sentinel"></param>
    /// <param name="singleSignOn"></param>
    /// <param name="cookieManager"></param>
    public AccountController(IUserManager userManager, ISentinel sentinel, ISingleSignOn singleSignOn, ICookieManager cookieManager, ITranslatorFactory translatorFactory)
    {
        _userManager = userManager;
        _sentinel = sentinel;
        _singleSignOn = singleSignOn;
        _cookieManager = cookieManager;
        _translatorFactory = translatorFactory;
    }

    private const string AccountProfileUrl = "~/account/profile";
    private const string RootUrl = "/";
    private const string ProfileUrl = "~/profile";
    
    /// <summary>
    /// Returns the login page for the system
    /// </summary>
    /// <param name="login"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("login")]
    [AllowAnonymous]
    [AccessKey(SecurityAccess.Everyone)]
    [HttpGet]
    public async Task<IActionResult> LoginGet(Login? login, CancellationToken cancellationToken)
    {
        var ssoId = await _cookieManager.GetSsoIdFromSsoIdCookie(Request);
        if (ssoId.HasValue)
        {
            await _cookieManager.DeleteSsoIdCookie(Response);
            var url = await _singleSignOn.StartSingleSignOn(ssoId.Value, cancellationToken);

            return Ok(new Redirect { RedirectTo = url });
        }

        login ??= new Login();

        return LoginView(login);
    }

    private ActionResult LoginView(Login? login)
    {
        return PartialView("Login", login);
    }

    /// <summary>
    /// Used for processing the login attempts
    /// </summary>
    /// <param name="login"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("login")]
    [AllowAnonymous]
    [AccessKey(SecurityAccess.Everyone)]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> LoginPost(Login login, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(login.Password))
        {
            var url = await _singleSignOn.StartSingleSignOn(login.Email, cancellationToken);
            if (!string.IsNullOrWhiteSpace(url))
                return Redirect(url);

            if (login.ForgotPassword)
            {
                await _userManager.ForgotPassword(login.Email, cancellationToken);
            }

            return LoginView(login);
        }

        if (login.ForgotPassword)
        {
            await _userManager.ForgotPassword(login.Email, cancellationToken);
            return LoginView(login);
        }

        var loginResponse = await _userManager.Login(login, cancellationToken);
        ViewBag.loginResponse = loginResponse.Result;
        switch (loginResponse.Result)
        {
            case LoginResult.Success:
                await _cookieManager.CreateSessionCookie(Response, loginResponse);

                return Redirect("/");
            case LoginResult.EmailNotConfirmed:
            case LoginResult.TwoFactorAuthenticationRemovalRequested:
            case LoginResult.TwoFactorAuthenticationCodeRequired:
            case LoginResult.TwoFactorAuthenticationCodeIncorrect:
                return LoginView(login);
            case LoginResult.Failed:
            default:
                await _sentinel.LogBadRequest(this, cancellationToken);
                return LoginView(login);
        }
    }

    /// <summary>
    /// Logs a user out of the system and tidies up any cookies.
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("logout")]
    [HttpGet]
    [AllowAnonymous]
#pragma warning disable IDE0060 // Remove unused parameter cancellationToken - used here as I want this parameter on all controller methods.
    public async Task<IActionResult> Logout(CancellationToken cancellationToken)
#pragma warning restore IDE0060 // Remove unused parameter
    {
        await _cookieManager.DeleteSessionCookie(Response);
        await _cookieManager.DeleteSsoIdCookie(Response);
        return Redirect("~/");
    }

    /// <summary>
    /// Called by an identity provider when processing an OAuth2 identity request.
    /// </summary>
    /// <param name="ssoId"></param>
    /// <param name="code"></param>
    /// <param name="scope"></param>
    /// <param name="authuser"></param>
    /// <param name="prompt"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("auth/{ssoId}")]
    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> Auth([FromRoute] long ssoId, [FromQuery] string code, [FromQuery] string scope, [FromQuery] string authuser, [FromQuery] string prompt, CancellationToken cancellationToken)
    {
        var ssoUserId = await _singleSignOn.ExchangeAuthorisationToken(ssoId, code, cancellationToken);
        
        var cookieLink = await _cookieManager.GetUserIdFromLinkCookie(Request, cancellationToken);
 
        if (cookieLink?.User != null)
        {
            await _cookieManager.DeleteLinkCookie(Response);

            //Creating my own Audit details as the JWT isn't present when being called from an outside source
            var auditUserDetails = new AuditUserDetails
            {
                UserId = cookieLink.User.Id,
                UserDisplayName = cookieLink.User.DisplayName
            };

            await _userManager.LinkSsoProfileToUser(auditUserDetails, cookieLink.User, ssoId, ssoUserId, cookieLink.LinkType == LinkType.NewUser , cancellationToken);

            if (cookieLink.LinkType == LinkType.Profile)
                return Redirect(ProfileUrl);
        }
        
        var loginResponse = await _userManager.LoginSso(ssoId, ssoUserId, cancellationToken);

        switch (loginResponse.Result)
        {
            case LoginResult.Success:
                await _cookieManager.CreateSessionCookie(Response, loginResponse);
                await _cookieManager.CreateSsoIdCookie(Response, ssoId);

                return Redirect("~/");
            case LoginResult.EmailNotConfirmed:
            case LoginResult.TwoFactorAuthenticationRemovalRequested:
            case LoginResult.TwoFactorAuthenticationCodeRequired:
            case LoginResult.TwoFactorAuthenticationCodeIncorrect:
                return Redirect("~/");
            case LoginResult.Failed:
            default:
                await _sentinel.LogBadRequest(this, cancellationToken);
                return Redirect("~/");
        }
    }

    /// <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 id = User.GeneralIdRef();
        var loginResponse = await _userManager.RefreshToken(id, cancellationToken);

        await _cookieManager.CreateSessionCookie(Response, loginResponse);

        return Ok();
    }
    
    /// <summary>
    /// Page used for making alterations to a users profile.
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("profile")]
    [HttpGet]
    [AccessKey(SecurityAccess.Everyone)]
    public async Task<IActionResult> ProfileGet(CancellationToken cancellationToken)
    {
        var profile = await _userManager.GetProfile(User.Email(), cancellationToken);
        Models.UserProfile userProfile = profile.ToUserProfile();

        _translatorFactory.Locale = profile.PreferredLocale;
        var profileViewModel = new ProfileViewModel
        {
            Profile = userProfile,
            TranslatorFactory = _translatorFactory
        };


        return PartialView("Profile", profileViewModel);
    }

    /// <summary>
    /// Used to make updates to a users profile.
    /// </summary>
    /// <param name="userProfile"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("profile")]
    [AccessKey(SecurityAccess.Everyone)]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ProfilePost(Models.UserProfile userProfile, CancellationToken cancellationToken)
    {
        var updatedUserProfile = new UpdatedUserProfile
        {
            Email = User.Email(),
            Password = userProfile.Password ?? string.Empty,
            FirstName = userProfile.FirstName ?? string.Empty,
            MiddleNames = userProfile.MiddleNames ?? string.Empty,
            LastName = userProfile.LastName ?? string.Empty,
            SecurityCode = userProfile.SecurityCode ?? string.Empty,
            UsingTwoFactorAuthentication = userProfile.UsingTwoFactorAuthentication
        };

        await _userManager.UpdateProfile(AuditUserDetails, User.Email(), updatedUserProfile, cancellationToken);

        var profile = await _userManager.GetProfile(User.Email(), cancellationToken);

        var id = User.GeneralIdRef();

        if (profile.DomainSsoProviderId == null)
        {
            if (profile.SsoProviderId != userProfile.SsoProviderId)
            {
                if (userProfile.SsoProviderId != -1)
                {
                    var url = await _singleSignOn.StartSingleSignOn(userProfile.SsoProviderId, cancellationToken);
                    if (!string.IsNullOrWhiteSpace(url))
                    {
                        await _cookieManager.CreateProfileLinkCookie(Response, AuditUserDetails, id, cancellationToken);

                        return Redirect(url);
                    }
                }
                else
                {
                    await _userManager.TurnOfSsoForUser(AuditUserDetails, id, cancellationToken);
                }
            }
        }

        return Redirect(AccountProfileUrl);
    }

    /// <summary>
    /// Page used for making alterations to a users profile.
    /// </summary>
    /// <param name="token"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("confirmaccount/{token}")]
    [HttpGet]
    [AllowAnonymous]
    [AccessKey(SecurityAccess.Everyone)]
    public async Task<IActionResult> ConfirmAccountGet([FromRoute] string token, CancellationToken cancellationToken)
    {
        var emailActionToken = DecodeToken(token);

        var profile = await _userManager.GetProfile(emailActionToken?.Email!, cancellationToken);
        var userProfile = profile.ToConfirmEmailAccount();

        return View("ConfirmAccount", userProfile);
    }

    private static EmailActionToken? DecodeToken(string token)
    {
        var jsonString =  Convert.FromBase64String(token);
        var emailActionToken = JsonSerializer.Deserialize<EmailActionToken>(jsonString);
        return emailActionToken;
    }

    /// <summary>
    /// Used to make updates to a users profile.
    /// </summary>
    /// <param name="userProfile"></param>
    /// <param name="token"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [Route("confirmaccount/{token}")]
    [AllowAnonymous]
    [AccessKey(SecurityAccess.Everyone)]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ConfirmAccountPost(
        Models.ConfirmEmailAccount userProfile,
        [FromRoute] string token,
        CancellationToken cancellationToken
    )
    {
        var emailActionToken = DecodeToken(token);
        var user = await _userManager.GetUserByEmailAsync(emailActionToken?.Email!, cancellationToken) ??
                   throw new NotFoundException("User not found");

        var auditUserDetails = new AuditUserDetails
        {
            UserId = user.Id,
            UserDisplayName = user.DisplayName
        };


        if (user.Domain.SsoProviderId is not null || userProfile.SsoProviderId != -1)
        {
            var url = await _singleSignOn.StartSingleSignOn(user.Domain.SsoProviderId ?? userProfile.SsoProviderId, cancellationToken);
            if (!string.IsNullOrWhiteSpace(url))
            {
                await _cookieManager.CreateNewUserLinkCookie(Response, auditUserDetails, user.ToGeneralIdRef()!,
                    cancellationToken);

                return Redirect(url);
            }
        }
        else
        {
            await _userManager.TurnOfSsoForUser(auditUserDetails, user.ToGeneralIdRef()!, cancellationToken);

            var userAuthenticationDetails = new UserAuthenticationDetails
            {
                Id = user.ToGeneralIdRef()!,
                Password = userProfile.Password ?? string.Empty,
                SecurityCode = userProfile.SecurityCode ?? string.Empty,
                UsingTwoFactorAuthentication = userProfile.UsingTwoFactorAuthentication
            };

            await _userManager.SetAuthentication(auditUserDetails, userAuthenticationDetails, true, cancellationToken);
        }

        return Redirect(RootUrl);
    }
}