﻿using System;
using System.Diagnostics;
using System.IO;
using HomeBarman.Domain.Database;
using HomeBarman.Domain.UnitOfWork;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting.Json;
using SimpleInjector;
using SimpleInjector.Integration.AspNetCore;
using SimpleInjector.Integration.AspNetCore.Mvc;
using Swashbuckle.Swagger.Model;

namespace AnotherAspNetSite
{
    public class Startup
    {
        [NotNull]
        private readonly Container _container = new Container();

        private readonly string _contentRootPath;

        public Startup([NotNull]IHostingEnvironment env)
        {
            Log.Logger = new LoggerConfiguration()
              .Enrich.FromLogContext()
              .WriteTo.LiterateConsole()
              .WriteTo.RollingFile(new JsonFormatter(renderMessage: true), Path.Combine("logs", "log-{Date}.json"))
              .CreateLogger();

            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            // ReSharper disable PossibleNullReferenceException
            // ReSharper disable AssignNullToNotNullAttribute
            Configuration = builder.Build();
            // ReSharper restore AssignNullToNotNullAttribute
            // ReSharper restore PossibleNullReferenceException
            _contentRootPath = env.ContentRootPath;
        }

        [NotNull]
        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the _container.
        public void ConfigureServices([NotNull]IServiceCollection services)
        {
            services.AddCors();
            // Add framework services.
            services.AddMvc();

            services.AddSingleton<IControllerActivator>(
            new SimpleInjectorControllerActivator(_container));
            services.AddSingleton<IViewComponentActivator>(
                new SimpleInjectorViewComponentActivator(_container));

            #region Swagger

            var pathToDoc = Path.Combine(_contentRootPath, Configuration["Swagger:Path"]);

            services.AddSwaggerGen();
            services.ConfigureSwaggerGen(options =>
            {
                options.SingleApiVersion(new Info
                {
                    Version = "v1",
                    Title = "HomeBarman2 API",
                    TermsOfService = "None"
                });
                options.IncludeXmlComments(pathToDoc);
                options.DescribeAllEnumsAsStrings();
            });
            #endregion
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(
            [NotNull]IApplicationBuilder app,
            [NotNull] IHostingEnvironment env,
            [NotNull]ILoggerFactory loggerFactory,
            [NotNull]IApplicationLifetime appLifetime)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
            loggerFactory.AddSerilog(Log.Logger);

            // Ensure any buffered events are sent at shutdown
            appLifetime.ApplicationStopped.Register(Log.CloseAndFlush);

            #region IoC
            app.UseSimpleInjectorAspNetRequestScoping(_container);

            _container.Options.DefaultScopedLifestyle = new AspNetRequestLifestyle();

            InitializeContainer(app, loggerFactory);

            try
            {
                _container.Verify();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"DI verification failed:\n{ex}");
            }
            #endregion

            // todo switch to URL rewrite middleware in ASP.NET Core 1.1
            #region Fix for non-root saved pages

            //var rewriteOptions = new RewriteOptions()
            //    .AddRedirect("redirect-rule/(.*)", "redirected/$1")
            //    .AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2", skipRemainingRules: true)
            //    .AddApacheModRewrite(env.ContentRootFileProvider, "ApacheModRewrite.txt")
            //    .AddIISUrlRewrite(env.ContentRootFileProvider, "IISUrlRewrite.xml");
                //.Add(RedirectXMLRequests)
                //.Add(new RedirectImageRequests(".png", "/png-images"))
                //.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
            //app.UseRewriter(rewriteOptions);

            // this peace of code ensures that all virtual paths for the client are redirected to index page in a first place
            // if client is hosted separetaly (another domain), then this should be reviewed and removed
            app.Use(async (context, next) =>
            {
                await next();

                if (context.Response.StatusCode == 404 &&
                    !Path.HasExtension(context.Request.Path.Value) &&
                    !context.Request.Path.StartsWithSegments("/api") &&
                    !context.Request.Path.StartsWithSegments("/libs"))
                {
                    context.Request.Path = "/index.html"; // Put your Angular root page here 
                    await next();
                }
            });
            #endregion

            var clientSection = Configuration.GetSection("Client");
            var clientLocation = clientSection.GetValue<string>("Location");

            // this is to enable only client original location to be used to query for data
            // must be inserted before other built-in middlewares
            app.UseCors(builder =>
                builder
                .WithOrigins(clientLocation) // FE location
                .AllowAnyHeader()
                .AllowAnyMethod()); 

            app.UseDefaultFiles();
            app.UseStaticFiles();
            app.UseMvc();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            #region Swagger
            app.UseSwagger();
            app.UseSwaggerUi();
            #endregion
        }

        private void InitializeContainer([NotNull]IApplicationBuilder app, [NotNull]ILoggerFactory loggerFactory)
        {
            _container.RegisterSingleton(loggerFactory);
            _container.RegisterConditional(
                typeof(ILogger<>),
                c => typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType),
                Lifestyle.Singleton,
                c => true);

            // Add application presentation components:
            _container.RegisterMvcControllers(app);
            _container.RegisterMvcViewComponents(app);

            var connectionString = Configuration.GetConnectionString("AppBarmanDbContextConnectionString");
            
            _container.Register<AppBarmanDbContext>(Lifestyle.Scoped);
            _container.Register<DbContextOptions<AppBarmanDbContext>>(() =>
            {
                var builder = new DbContextOptionsBuilder<AppBarmanDbContext>();
                builder.UseLoggerFactory(loggerFactory)
                    .EnableSensitiveDataLogging()
                    .UseSqlServer(connectionString, optionsBuilder =>
                    {
                        optionsBuilder.MigrationsAssembly(typeof(AppBarmanDbContext).AssemblyQualifiedName);
                        optionsBuilder.MigrationsHistoryTable("migration_history");
                    });
                return builder.Options;
            }, Lifestyle.Singleton);

            // Add application services. For instance:
            _container.Register<IBarmanRepository, BarmanRepository>(Lifestyle.Scoped);

            // Cross-wire ASP.NET services (if any). For instance:
            //_container.RegisterSingleton(app.ApplicationServices.GetService<ILoggerFactory>());
            // NOTE: Prevent cross-wired instances as much as possible.
            // See: https://simpleinjector.org/blog/2016/07/
        }
    }
}
