using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

using Fab2ApprovalSystem.DMO;
using Fab2ApprovalSystem.Misc;
using Fab2ApprovalSystem.Models;

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;

using Newtonsoft.Json;

namespace Fab2ApprovalSystem.Controllers;

[Authorize]
public class AccountController : Controller {

    public AccountController()
        : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()))) {
    }

    public AccountController(UserManager<ApplicationUser> userManager) {
        UserManager = userManager;
    }

    public UserManager<ApplicationUser> UserManager { get; private set; }

    // GET: /Account/Login
    [AllowAnonymous]
    // try to make the browser refresh the login page every time, to prevent issues with changing usernames and the anti-forgery token validation
    [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
    public ActionResult Login(string returnUrl) {
        ViewBag.ReturnUrl = returnUrl;
        return View();
    }

    private void SetSessionParameters(LoginResult loginResult, LoginModel user) {
        Session["JWT"] = loginResult.AuthTokens.JwtToken;
        Session["RefreshToken"] = loginResult.AuthTokens.RefreshToken;

        Session[GlobalVars.SESSION_USERID] = user.UserID;
        Session[GlobalVars.SESSION_USERNAME] = user.FullName;
        Session[GlobalVars.IS_ADMIN] = user.IsAdmin;
        Session[GlobalVars.IS_MANAGER] = user.IsManager;
        Session[GlobalVars.OOO] = user.OOO;
        Session[GlobalVars.CAN_CREATE_PARTS_REQUEST] = user.IsAdmin || PartsRequestController.CanCreatePartsRequest(user.UserID);

        FormsAuthentication.SetAuthCookie(user.LoginID, true);
    }

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginModel model, string returnUrl) {
        try {
            bool isLoginValid;

            HttpClient httpClient = HttpClientFactory.Create();
            httpClient.BaseAddress = new Uri(GlobalVars.AppSettings.ApiBaseUrl);

            LoginResult loginResult = await AccountDMO.LoginAsync(httpClient, model);

#if (DEBUG)
            isLoginValid = true;

#endif
#if (!DEBUG)

            bool isIFX = false;
            if (GlobalVars.DBConnection.ToUpper() == "TEST" || GlobalVars.DBConnection.ToUpper() == "QUALITY") {
                isLoginValid = true;
            } else {
                isLoginValid = loginResult.IsAuthenticated;
                if (isLoginValid)
                    isIFX = true;

            }

#endif

            if (isLoginValid) {
                UserAccountDMO userDMO = new UserAccountDMO();
                LoginModel user = userDMO.GetUser(model.LoginID);
                if (user != null) {
                    SetSessionParameters(loginResult, user);

                    return RedirectToLocal(returnUrl);
                } else {
                    ModelState.AddModelError("", "The user name does not exist in the DB. Please contact the System Admin");
                }
            } else {
                ModelState.AddModelError("", "The user name or password provided is incorrect.");
            }
        } catch (Exception ex) {
            Functions.WriteEvent(GlobalVars.AppSettings, @User.Identity.Name + " " + ex.InnerException, System.Diagnostics.EventLogEntryType.Error);
            EventLogDMO.Add(new WinEventLog() { IssueID = 99999, UserID = @User.Identity.Name, DocumentType = "Login", OperationType = "Error", Comments = "Reject - " + ex.Message });
            ModelState.AddModelError("", ex.Message);
        }

        return View(model);
        // If we got this far, something failed, redisplay form
    }

    [HttpPost]
    [AllowAnonymous]
    public async Task<HttpResponseMessage> ExternalAuthSetup(AuthAttempt authAttempt) {
        try {
            bool isLoginValid;

            HttpClient httpClient = HttpClientFactory.Create();
            httpClient.BaseAddress = new Uri(GlobalVars.AppSettings.ApiBaseUrl);

            LoginResult loginResult = await AccountDMO.ExternalAuthSetupAsync(httpClient, authAttempt);

#if (DEBUG)
            isLoginValid = true;

#endif
#if (!DEBUG)

            bool isIFX = false;
            if (GlobalVars.DBConnection.ToUpper() == "TEST" || GlobalVars.DBConnection.ToUpper() == "QUALITY") {
                isLoginValid = true;
            } else {
                isLoginValid = loginResult.IsAuthenticated;
                if (isLoginValid)
                    isIFX = true;

            }

#endif

            if (isLoginValid) {
                UserAccountDMO userDMO = new UserAccountDMO();
                LoginModel user = userDMO.GetUser(authAttempt.LoginID);
                if (user != null) {
                    SetSessionParameters(loginResult, user);

                    return new HttpResponseMessage(HttpStatusCode.OK);
                } else {
                    ModelState.AddModelError("", "The user name does not exist in the DB. Please contact the System Admin");

                    return new HttpResponseMessage(HttpStatusCode.NotFound);
                }
            } else {
                ModelState.AddModelError("", "The user name or password provided is incorrect.");

                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }
        } catch (Exception ex) {
            Functions.WriteEvent(GlobalVars.AppSettings, @User.Identity.Name + " " + ex.InnerException, System.Diagnostics.EventLogEntryType.Error);
            EventLogDMO.Add(new WinEventLog() { IssueID = 99999, UserID = @User.Identity.Name, DocumentType = "Login", OperationType = "Error", Comments = "Reject - " + ex.Message });
            ModelState.AddModelError("", ex.Message);

            return new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }
    }

    // GET: /Account/Register
    [AllowAnonymous]
    public ActionResult Register() {
        return View();
    }

    // POST: /Account/Disassociate
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Disassociate(string loginProvider, string providerKey) {
        ManageMessageId? message = null;
        IdentityResult result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey));
        if (result.Succeeded) {
            message = ManageMessageId.RemoveLoginSuccess;
        } else {
            message = ManageMessageId.Error;
        }
        return RedirectToAction("Manage", new { Message = message });
    }

    // GET: /Account/Manage
#pragma warning disable IDE0060 // Remove unused parameter
    public ActionResult Manage(ManageMessageId? message) {
        return View();
    }
#pragma warning restore IDE0060 // Remove unused parameter

    // POST: /Account/ExternalLogin
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult ExternalLogin(string provider, string returnUrl) {
        // Request a redirect to the external login provider
        return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
    }

    // POST: /Account/LinkLogin
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LinkLogin(string provider) {
        // Request a redirect to the external login provider to link a login for the current user
        return new ChallengeResult(provider, Url.Action("LinkLoginCallback", "Account"), User.Identity.GetUserId());
    }

    // GET: /Account/LinkLoginCallback
    public async Task<ActionResult> LinkLoginCallback() {
        ExternalLoginInfo loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
        if (loginInfo == null) {
            return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
        }
        IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
        if (result.Succeeded) {
            return RedirectToAction("Manage");
        }
        return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
    }

    // POST: /Account/LogOff
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LogOff() {
        FormsAuthentication.SignOut();
        return RedirectToAction("Login", "Account");
    }

    // GET: /Account/ExternalLoginFailure
    [AllowAnonymous]
    public ActionResult ExternalLoginFailure() {
        return View();
    }

    [ChildActionOnly]
    public ActionResult RemoveAccountList() {
        IList<UserLoginInfo> linkedAccounts = UserManager.GetLogins(User.Identity.GetUserId());
        ViewBag.ShowRemoveButton = HasPassword() || linkedAccounts.Count > 1;
        return (ActionResult)PartialView("_RemoveAccountPartial", linkedAccounts);
    }

    protected override void Dispose(bool disposing) {
        if (disposing && UserManager != null) {
            UserManager.Dispose();
            UserManager = null;
        }
        base.Dispose(disposing);
    }

    #region Helpers
    // Used for XSRF protection when adding external logins
    private const string XsrfKey = "XsrfId";

    private IAuthenticationManager AuthenticationManager {
        get {
            return HttpContext.GetOwinContext().Authentication;
        }
    }

    private async Task SignInAsync(ApplicationUser user, bool isPersistent) {
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
        ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
        AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
    }

    private void AddErrors(IdentityResult result) {
        foreach (string error in result.Errors) {
            ModelState.AddModelError("", error);
        }
    }

    private bool HasPassword() {
        ApplicationUser user = UserManager.FindById(User.Identity.GetUserId());
        if (user != null) {
            return user.PasswordHash != null;
        }
        return false;
    }

    public enum ManageMessageId {
        ChangePasswordSuccess,
        SetPasswordSuccess,
        RemoveLoginSuccess,
        Error
    }

    private ActionResult RedirectToLocal(string returnUrl) {
        if (Url.IsLocalUrl(returnUrl)) {
            return Redirect(returnUrl);
        } else {
            return RedirectToAction("MyTasks", "Home");
        }
    }

    private class ChallengeResult : HttpUnauthorizedResult {
        public ChallengeResult(string provider, string redirectUri) : this(provider, redirectUri, null) {
        }

        public ChallengeResult(string provider, string redirectUri, string userId) {
            LoginProvider = provider;
            RedirectUri = redirectUri;
            UserId = userId;
        }

        public string LoginProvider { get; set; }
        public string RedirectUri { get; set; }
        public string UserId { get; set; }

        public override void ExecuteResult(ControllerContext context) {
            AuthenticationProperties properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
            if (UserId != null) {
                properties.Dictionary[XsrfKey] = UserId;
            }
            context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
        }
    }
    #endregion
}