using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;

using BeWo.Data.Access;
using BeWo.Data.Entities;

using BS.Shared.Core;

using log4net.Config;

using NHibernate;
using NHibernate.Cfg;

namespace BeWo.Data
{
    public class ASPHibernateSessionManager : IHttpModule
    {
        public void Dispose()
        {
        }

        public void Init(HttpApplication context)
        {
            context.AcquireRequestState += (s, e) =>
                {
                    Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE");

                    if (ShouldConfigureHibernateSession(context))
                    {
                        // TEST FOR DEVEXPRESS. PROBLEMS WITH THIS MODULE WHEN USING AJAX!
                        if (HttpContext.Current.Session == null)
                            return;


                        // ---------------------------------------------------------------
                        if (!this.ConfigureHibernateSession(context))
                            return;

                        // throw new Exception("Could not configure database. No tenant parameter found");
                        bool userLoaded = TryAuthenticateWithRequestParams(context);
                        if (!userLoaded)
                            userLoaded = TryAuthenticateWithLoginForm(context);

                        if (!userLoaded && SessionFacade.LoggedInUser != null)
                            SessionFacade.LoggedInUser =
                                DAOFactory.GenericDAO.LoadByID<ApplicationUser>(SessionFacade.LoggedInUser.Oid.Value);
                    }
                };

            context.EndRequest += (s, e) =>
                {
                    if (HttpContext.Current.Items["hibernateSession"] != null)
                        ((ISession)HttpContext.Current.Items["hibernateSession"]).Close();
                };
        }

        private static bool ShouldConfigureHibernateSession(HttpApplication context)
        {
            if (!String.IsNullOrEmpty(context.Request.FilePath) && context.Request.FilePath.IndexOf("Admin.aspx") >= 0)
                return false;
            return true;
        }

        private bool ConfigureHibernateSession(HttpApplication context)
        {
	        var lTenant = GetTenant(context);
			
            if (!String.IsNullOrEmpty(lTenant))
            {
                SessionFacade.Tenant = lTenant;
            }
            else
            {
                lTenant = SessionFacade.Tenant;
            }

            if (String.IsNullOrEmpty(lTenant))
            {
                return false;
            }
			
            var _SessionFactory = new Configuration().Configure(context.Server.MapPath(@"Multitenancy\" + lTenant + ".config")).BuildSessionFactory();
			
            XmlConfigurator.Configure();
            HttpContext.Current.Items.Add("hibernateSession", _SessionFactory.OpenSession());

            return true;
        }

		private static String GetTenant(HttpApplication context)
	    {
			var lTenant = String.Empty;

			String token = context.Request.Params["token"];
			if (!String.IsNullOrEmpty(token))
				lTenant = GetLoginInfoFromToken(token)["tenant"];

			if (String.IsNullOrEmpty(lTenant))
				lTenant = context.Request.Params["tenant"];

			if (String.IsNullOrEmpty(lTenant))
				lTenant = context.Request.Params["knr"];

			if (String.IsNullOrEmpty(lTenant))
				lTenant = context.Request.Params["kunde"];

			if (String.IsNullOrEmpty(lTenant))
				lTenant = context.Request.Params["k"];

			return lTenant;
	    }

        private static bool TryAuthenticateWithLoginForm(HttpApplication context)
        {
            var name = context.Request.Params["ctl00$tb_login"];
            var password = context.Request.Params["ctl00$tb_password"];

            if (!Utils.IsAnyNullOrEmpty(name, password))
            {
                ApplicationUser user = DAOFactory.UserDAO.FindUserByLoginName(name);

                if (user != null && DAOFactory.UserDAO.CheckPassword(user, password))
                {
                    SessionFacade.LoggedInUser = user;
                    return true;
                }
            }

            return false;
        }

        private static bool TryAuthenticateWithRequestParams(HttpApplication context)
        {
            string lUserName = null;
			string lPassword = null;

            //if (!context.Request.Params.AllKeys.Contains("tenant"))
            //    return false;
			var lTenant = GetTenant(context);

			if (!String.IsNullOrEmpty(lTenant))
            {
				if (context.Request.Params.AllKeys.Contains("token")) 
				{
					lUserName = GetLoginInfoFromToken(context.Request.Params["token"])["user"];
					lPassword = GetLoginInfoFromToken(context.Request.Params["token"])["password"];
				}
            }
            else
            {
                lUserName = context.Request.Params["username"];
                lPassword = context.Request.Params["token"];
            }

            if (!Utils.IsAnyNullOrEmpty(lUserName, lPassword))
            {
                string ip = null;
                if (context.Request != null)
                {
                    ip = context.Request.UserHostAddress;
                }

                ApplicationUser lUser = DAOFactory.UserDAO.FindUserByLoginName(lUserName, ip);

                if (lUser != null && lUser.CheckRC2Password(lPassword))
                {
                    SessionFacade.LoggedInUser = lUser;
                    return true;
                }
            }

            return false;
        }

        private static Dictionary<string, string> GetLoginInfoFromToken(string token)
        {
            if(string.IsNullOrWhiteSpace(token))
                return new Dictionary<string, string>();

            var decodedToken = string.Empty;

            token = token.Replace(' ', '+');
            token = token.Trim(',');

            try
            {
                decodedToken = DecryptString(HttpUtility.UrlDecode(token));
            }
            catch (Exception)
            {
                decodedToken = DecryptString(token);
            }

            if (decodedToken.Split(';')[2].Split('=')[0].Equals("myToken"))
            {
                var b = decodedToken.Split(';');

                var a = DecryptString(new Regex(@"[\=]{1}").Split(b[2], 2)[1]);
                decodedToken += ";" + a;
            }

            var Params = decodedToken.Split(';');

            return Params.Select(item => item.Split('=')).Where(a => a[0].Equals("tenant") || a[0].Equals("password") || a[0].Equals("user")).ToDictionary(a => a[0], a => a[1]);
        }

        private static string DecryptString(string strToDecrypt)
        {
            if (string.IsNullOrWhiteSpace(strToDecrypt))
                return "Beim Decodieren ist leider ein Fehler aufgetreten.";

            strToDecrypt = strToDecrypt.Replace(' ', '+');

            if(strToDecrypt.Contains(","))
                strToDecrypt = strToDecrypt.Split(',')[0];

            byte[] _Rc2IV = { 35, 138, 177, 253, 227, 63, 2, 27 };
            byte[] _Rc2Key = { 174, 130, 219, 185, 185, 221, 96, 50, 37, 212, 81, 121, 71, 206, 130, 153 };
            var lRc2CSP = new RC2CryptoServiceProvider();
            var lDecryptor = lRc2CSP.CreateDecryptor(_Rc2Key, _Rc2IV);

            strToDecrypt = strToDecrypt.Replace(' ', '+');
            strToDecrypt = strToDecrypt.Trim(',');

            using (var msDecrypt = new MemoryStream(Convert.FromBase64String(strToDecrypt)))
            {
                using (var csDecrypt = new CryptoStream(msDecrypt, lDecryptor, CryptoStreamMode.Read))
                {
                    var bytes = new List<byte>();
                    int b;

                    do
                    {
                        b = csDecrypt.ReadByte();

                        if (b != -1)
                            bytes.Add(Convert.ToByte(b));

                    } while (b != -1);

                    return Encoding.UTF8.GetString(bytes.ToArray());
                }
            }
        }

    }
}