using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FluentValidation.AspNetCore;
using GreenTree.Strohrmann.ERP.Core.Helper;
using GreenTree.Strohrmann.ERP.Domain.Model;
using GreenTree.Strohrmann.ERP.Services.Authentication;
using GreenTree.Strohrmann.ERP.Services.Authorization;
using GreenTree.Strohrmann.ERP.Services.Geolocator;
using GreenTree.Strohrmann.ERP.Services.Notification;
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.Mvc.Razor;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace GreenTree.Strohrmann.ERP.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",
            "Craft-View",
            "Craft-Change",
            "Craft-Delete",
            "Customer-View",
            "Customer-Change",
            "Customer-Delete",
            "Employee-View",
            "Employee-Change",
            "Employee-Delete",
            "Material-View",
            "Material-Change",
            "Material-Delete",
            "Supplier-View",
            "Supplier-Change",
            "Supplier-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();

            // Add option handling
            services.AddOptions();

            // 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 global mail notification options
            var mailNotificationOptions = Configuration.GetSection("MailNotificationOptions").Get<MailNotificationOptions>();

            if (mailNotificationOptions == null)
                throw new Exception("The appsettings.json does not contain mail notification options.");

            services.AddSingleton<IMailNotificationOptions>(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 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<ERPDbContext>(options =>
            {
                options.UseMySql(Configuration.GetConnectionString("ERPDatabase"));
                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>();
        }

        // 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.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<ERPDbContext>())
                {
                    context.Database.Migrate();
                }
            }
        }
    }
}
