using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FluentValidation.AspNetCore;
using GreenTree.Maschinenbestellungen.Core.Helper;
using GreenTree.Maschinenbestellungen.Domain.Model;
using GreenTree.Maschinenbestellungen.Services.Authentication;
using GreenTree.Maschinenbestellungen.Services.Authorization;
using GreenTree.Maschinenbestellungen.Services.Geolocator;
using GreenTree.Maschinenbestellungen.Services.Localization;
using GreenTree.Maschinenbestellungen.Services.Notification;
using GreenTree.Maschinenbestellungen.Web.Configuration;
using GreenTree.Maschinenbestellungen.Web.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Porta.Kundenzähler.Services.Extension;

namespace GreenTree.Maschinenbestellungen.Web
{
    public class Startup
    {
        #region Policies

        /// <summary>
        /// All available policies in the application
        /// </summary>
        public static readonly string[] _availablePolicies =
        {
            "User-View",
            "User-Change",
            "User-Delete"
        };

        #endregion

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add MVC controller and views
            services.AddControllersWithViews(options =>
            {
                //options.ModelBinderProviders.Insert(0, new CustomerModelBinderProvider());
            });

            // Add option handling
            services.AddOptions();
            services.AddSingleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>));

            // Add the HttpContextAccessor as Singleton
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            // Add global administration notification options
            var administrationOptions = Configuration.GetSection("AdministrationOptions").Get<AdministrationOptions>();

            if (administrationOptions == null)
                throw new Exception("The appsettings.json does not contain administration options.");

            services.AddSingleton(administrationOptions);

            // Add MailConfigurationOptions monitor
            if (!Configuration.SectionExists("MailNotificationOptions"))
                throw new Exception("The appsettings.json does not contain mail notification options.");

            services.Configure<MailNotificationOptions>(Configuration.GetSection("MailNotificationOptions"));

            // Add the mail notification service
            services.AddSingleton<INotificationService, MailNotificationService>();

            // Add global Google API options
            var geocodingOptions = Configuration.GetSection("GoogleApiOptions").Get<GoogleApiOptions>();

            if (geocodingOptions == null)
                throw new Exception("The appsettings.json does not contain Google API options.");

            services.AddSingleton(geocodingOptions);

            // Add the Google Geocoding service
            services.AddSingleton<IGeocodingService, GoogleGeocodingService>();

            // Add global culture options
            services.Configure<CultureOptions>(Configuration.GetSection("CultureOptions"));

            // Add localization
            services.Configure<RequestLocalizationOptions>(options =>
            {
                // Add global culture options
                var cultureOptions = Configuration.GetSection("CultureOptions").Get<CultureOptions>();

                var culture = cultureOptions == null || (cultureOptions.DefaultCulture != null && String.IsNullOrEmpty(cultureOptions.DefaultCulture))
                    ? CultureInfo.CurrentCulture.Name
                    : cultureOptions.DefaultCulture;

                options.DefaultRequestCulture = new RequestCulture(culture);
                options.RequestCultureProviders = new List<IRequestCultureProvider>
                {
                    new QueryStringRequestCultureProvider(),
                    new CookieRequestCultureProvider()
                };
            });

            // Add sessioning
            services.AddSession(options =>
            {
                var sessionOptions = Configuration.GetSection("SessionOptions").Get<SessionOptions>();

                options.IdleTimeout = sessionOptions.IdleTimeout;
                options.Cookie.Name = sessionOptions.Cookie.Name;
            });

            // Add Counter DbContext
            services.AddDbContextPool<OrderDbContext>(options =>
            {
                options.UseMySql(Configuration.GetConnectionString("OrderDatabase"));
                options.UseLazyLoadingProxies();
            });

            // Add user helper service
            services.AddScoped<IUserHelper, UserHelper>();

            // Add MVC with FluentValidation reference
            services.AddMvc()
              .AddFluentValidation(fv => fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()));

            // Add authentication
            services.AddAuthentication(options => 
                {
                    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                })
                .AddCookie(options => 
                {
                    options.Cookie.HttpOnly = true;
                    options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                    options.Cookie.SameSite = SameSiteMode.Strict;
                    options.LoginPath = "/Account/Login";
                    options.LogoutPath = "/Account/Logoff";
                    options.ExpireTimeSpan = new TimeSpan(0, 24, 0, 0);
                });

            // Add the default custom authentication service
            services.AddScoped<Services.Authentication.IAuthenticationService, DbContextAuthenticationService>();

            // Add the default authorization handler
            services.AddScoped<IAuthorizationHandler, DefaultAuthorizationHandler>();

            services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicy(
                    new[] { new DefaultAuthorizationPolicy(String.Empty) },
                    new[] { CookieAuthenticationDefaults.AuthenticationScheme });

                foreach (var policy in _availablePolicies)
                {
                    options.AddPolicy(policy, a =>
                    {
                        a.AuthenticationSchemes.Add(CookieAuthenticationDefaults.AuthenticationScheme);
                        a.RequireAuthenticatedUser();
                        a.AddRequirements(new DefaultAuthorizationPolicy(policy));
                    });
                }
            });

            // Add the DbContext custom authorization service
            services.AddScoped<Services.Authorization.IAuthorizationService, CookieAuthorizationService>();

            // Add the option monitoring service
            services.AddSingleton<IOptionMonitoringService, DefaultOptionMonitoringService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();
            app.UseRequestLocalization();

            app.UseRouting();

            app.UseAuthorization();
            app.UseAuthentication();
            app.UseCookiePolicy();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });

            using (var scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = scope.ServiceProvider.GetService<OrderDbContext>())
                {
                    context.Database.Migrate();
                }
            }
        }
    }
}
