using System.IdentityModel.Tokens.Jwt;
using System.IO.Compression;
using System.Reflection;
using System.Text;
using Hotline.Api.Filter;
using Hotline.Application;
using Hotline.Application.Jobs;
using Hotline.Caching.Interfaces;
using Hotline.CallCenter.Configs;
using Hotline.Configurations;
using Hotline.DI;
using Hotline.EventBus;
using Hotline.Identity;
using Hotline.Repository.SqlSugar;
using Hotline.Repository.SqlSugar.Ts;
using Hotline.Settings;
using Mapster;
using MapsterMapper;
using MediatR.Pipeline;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Quartz;
using Quartz.AspNetCore;
using XF.Domain.Cache;
using XF.Domain.Entities;
using XF.Domain.Exceptions;
using XF.Domain.Options;
using XF.Domain.Repository;
using XF.EasyCaching;

namespace Hotline.Api
{
    public static class StartupHelper
    {
        /// <summary>
        /// Authentication
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static IServiceCollection RegisterAuthentication(this IServiceCollection services, ConfigurationManager configuration)
        {
            var jwtOptions = configuration.GetSection("IdentityConfiguration").Get<IdentityConfiguration>().Jwt;

            #region remote ids

            //JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
            //services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            //    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, d =>
            //    {
            //        d.Authority = identityConfigs.IdentityUrl;
            //        d.RequireHttpsMetadata = false;
            //        d.TokenValidationParameters = new TokenValidationParameters
            //        {
            //            ValidateAudience = false
            //        };
            //        //d.Audience = "hotline_api";

            //        d.Events = new JwtBearerEvents
            //        {
            //            OnMessageReceived = context =>
            //            {
            //                var accessToken = context.Request.Query["access_token"];

            //                // If the request is for our hub...
            //                var path = context.HttpContext.Request.Path;
            //                if (!string.IsNullOrEmpty(accessToken) &&
            //                    (path.StartsWithSegments("/hubs/callcenter")))
            //                {
            //                    // Read the token out of the query string
            //                    context.Token = accessToken;
            //                }
            //                return Task.CompletedTask;
            //            }
            //        };
            //    });

            #endregion
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(d =>
                {
                    byte[] bytes = Encoding.UTF8.GetBytes(jwtOptions.SecretKey);
                    var secKey = new SymmetricSecurityKey(bytes);
                    d.TokenValidationParameters = new()
                    {
                        ValidateIssuer = false,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = secKey,
                        AudienceValidator = (audiences, token, parameters) =>
                        {
                            if (token is /*JwtSecurityToken*/ JsonWebToken jwtToken)
                            {
                                using var serviceProvider = services.BuildServiceProvider();
                                var cacheAudience = serviceProvider.GetService<ITypedCache<AudienceTicket>>();
                                var audience = cacheAudience.Get(jwtToken.Subject);
                                return audiences != null && audiences.Any(a => a == audience?.Ticket);
                            }

                            return false;
                        }
                    };

                    //d.Audience = "hotline_api";
                    d.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = context =>
                        {
                            var accessToken = context.Request.Query["access_token"];

                            // If the request is for our hub...
                            var path = context.HttpContext.Request.Path;
                            if (!string.IsNullOrEmpty(accessToken) &&
                                (path.StartsWithSegments("/hubs/hotline")))
                            {
                                // Read the token out of the query string
                                context.Token = accessToken;
                            }
                            return Task.CompletedTask;
                        }
                    };
                })
                ;

            return services;
        }

        public static IServiceCollection RegisterAuthorization(this IServiceCollection services, ConfigurationManager configuration)
        {
            services.AddAuthorization(options =>
            {
                options.AddPolicy(AppDefaults.AuthPolicy.Hotline, d => d.RequireClaim("scope", AppDefaults.AuthPolicy.Hotline));
            });
            return services;
        }

        /// <summary>
        /// Swagger
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection RegisterSwagger(this IServiceCollection services)
        {
            services.AddSwaggerGen(c =>
            {
                //添加文档
                c.SwaggerDoc("v1", new OpenApiInfo() { Title = "Hotline Api", Version = "v1.0", Description = "智慧热线api" });
                var files = Directory.GetFiles(AppContext.BaseDirectory).Where(d => d.EndsWith(".xml"));
                foreach (var file in files)
                {
                    c.IncludeXmlComments(file, true);
                }

                var scheme = new OpenApiSecurityScheme()
                {
                    Description = "Authorization header. \r\nExample: 'Bearer ***'",
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Authorization"
                    },
                    Scheme = "oauth2",
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.ApiKey,
                };
                c.AddSecurityDefinition("Authorization", scheme);
                var requirement = new OpenApiSecurityRequirement();
                requirement[scheme] = new List<string>();
                c.AddSecurityRequirement(requirement);
            });

            return services;
        }

        /// <summary>
        /// Cors
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <param name="corsOrigins"></param>
        /// <returns></returns>
        public static IServiceCollection RegisterCors(this IServiceCollection services, ConfigurationManager configuration, string corsOrigins)
        {
            services.AddCors(options =>
            {
                options.AddPolicy(name: corsOrigins,
                    builder =>
                    {
                        var origins = configuration.GetSection("Cors:Origins").Get<string[]>();
                        builder.SetIsOriginAllowed(a =>
                            {
                                return origins.Any(origin => origin.StartsWith("*.", StringComparison.Ordinal)
                                    ? a.EndsWith(origin[1..], StringComparison.Ordinal)
                                    : a.Equals(origin, StringComparison.Ordinal));
                            })
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .AllowCredentials();
                    });
            });

            return services;
        }

        /// <summary>
        /// Mapper
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection RegisterMapper(this IServiceCollection services)
        {
            var config = TypeAdapterConfig.GlobalSettings;
            config.ForDestinationType<IDataPermission>()
                .Ignore(d => d.CreatorId)
                .Ignore(d => d.CreatorOrgId)
                //.Ignore(d => d.CreatorOrgCode)
                .Ignore(d => d.AreaId);
            config.ForDestinationType<IWorkflow>()
                .Ignore(d => d.ExpiredTimeConfigId);
            config.ForDestinationType<IHasCreationTime>()
                .Ignore(d => d.CreationTime);
            config.ForDestinationType<IHasDeletionTime>().Ignore(d => d.DeletionTime);
            config.ForDestinationType<ISoftDelete>().Ignore(d => d.IsDeleted);
            config.ForDestinationType<IHasModificationTime>().Ignore(d => d.LastModificationTime);
            config.ForDestinationType<Entity>().Ignore(d => d.Id);

            services.AddSingleton(config);
            services.AddScoped<IMapper, ServiceMapper>();

            return services;
        }

        /// <summary>
        /// SignalR
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static IServiceCollection RegisterSignalR(this IServiceCollection services, ConfigurationManager configuration)
        {
            //var connectionString = configuration.GetConnectionString("Redis");
            //if (string.IsNullOrEmpty(connectionString))
            //    throw new UserFriendlyException("未配置signalR的redis连接");
            //services.AddSignalR().AddStackExchangeRedis(connectionString, options =>
            //{
            //    options.Configuration.ChannelPrefix = "Hotline:signalr:";
            //});
            var cacheConfig = configuration.GetRequiredSection("Cache").Get<CacheOptions>();
            services.AddSignalR().AddStackExchangeRedis($"{cacheConfig.Host}:{cacheConfig.Port}", options =>
            {
                options.Configuration.ChannelPrefix = "Hotline:signalr:";
                options.Configuration.Password = cacheConfig.Password;
                options.Configuration.DefaultDatabase = cacheConfig.Database;
            });
            return services;
        }

        public static IServiceCollection RegisterJob(this IServiceCollection services, AppConfiguration appConfiguration)
        {
            services.AddQuartz(d =>
            {
                d.SchedulerId = "default_scheduler";
                d.InterruptJobsOnShutdown = true;
                d.InterruptJobsOnShutdownWithWait = true;
                d.MaxBatchSize = 3;

                var apptaskJob = new JobKey(nameof(ApptaskJob));
                d.AddJob<ApptaskJob>(apptaskJob);
                d.AddTrigger(t => t
                    .WithIdentity("apptask-trigger")
                    .ForJob(apptaskJob)
                    .StartNow()
                    .WithCronSchedule("0/3 * * * * ?")
                );


                //即将超期和超期短信
                //var autoSendOverTimeSmsKey = new JobKey(nameof(SendOverTimeSmsJob), "send overtime order task");
                //d.AddJob<SendOverTimeSmsJob>(autoSendOverTimeSmsKey);
                //d.AddTrigger(t => t
                //    .WithIdentity("task-send-overtime-order-trigger")
                //    .ForJob(autoSendOverTimeSmsKey)
                //    .StartNow()
                //    .WithCronSchedule("0 30 09,14 * * ?"));

                var autoSendOrderKey = new JobKey(nameof(SendOrderJob), "send order task");
                switch (appConfiguration.AppScope)
                {
                    //智能化任务
                    case AppDefaults.AppScope.YiBin:
                        var aiVisitStatusKey = new JobKey(nameof(CheckAiVisitStateJob), "check aivisit state task");
                        d.AddJob<CheckAiVisitStateJob>(aiVisitStatusKey);
                        d.AddTrigger(t => t
                        .WithIdentity("task-check-aivisit-state-trigger")
                        .ForJob(aiVisitStatusKey)
                        .StartNow()
                        .WithCronSchedule("0 0/5 * * * ? *"));

                        //d.AddJob<SendOrderJob>(autoSendOrderKey);
                        //d.AddTrigger(t => t
                        //    .WithIdentity("task-send-order-trigger")
                        //    .ForJob(autoSendOrderKey)
                        //    .StartNow()
                        //    .WithCronSchedule("0 10 9 * * ?")
                        //);
                        break;
                    case AppDefaults.AppScope.ZiGong:
                        //d.AddJob<SendOrderJob>(autoSendOrderKey);
                        //d.AddTrigger(t => t
                        // .WithIdentity("task-send-order-trigger")
                        // .ForJob(autoSendOrderKey)
                        // .StartNow()
                        // .WithCronSchedule("0 10 9 * * ?")
                        //);
                        break;
                    default:
                        break;
                }

                switch (appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType)
                {
                    case AppDefaults.CallCenterType.XingTang:
                        var getCallsJobKey = new JobKey(nameof(XingTangCallsSyncJob));
                        d.AddJob<XingTangCallsSyncJob>(getCallsJobKey);
                        d.AddTrigger(t => t
                            .WithIdentity("get-callsxt-trigger")
                            .ForJob(getCallsJobKey)
                            .StartNow()
                            .WithCronSchedule("0/5 * * * * ?")
                        );

                        var getOperationsJobKey = new JobKey(nameof(XingTangTelOperationSyncJob));
                        d.AddJob<XingTangTelOperationSyncJob>(getOperationsJobKey);
                        d.AddTrigger(t => t
                            .WithIdentity("get-operationsxt-trigger")
                            .ForJob(getOperationsJobKey)
                            .StartNow()
                            .WithCronSchedule("0/10 * * * * ?")
                        );

                        var getCallSatisfactionJobKey = new JobKey(nameof(XingTangCallSatisfactionSyncJob));
                        d.AddJob<XingTangCallSatisfactionSyncJob>(getCallSatisfactionJobKey);
                        d.AddTrigger(t => t
                            .WithIdentity("get-callsatisfaction-trigger")
                            .ForJob(getCallSatisfactionJobKey)
                            .StartNow()
                            .WithCronSchedule("0/30 * * * * ?")
                        );

                        break;
                }
            });

            services.AddQuartzServer(d =>
            {
                d.WaitForJobsToComplete = true;
                d.StartDelay = TimeSpan.FromSeconds(5);
            });

            return services;
        }

        public static IServiceCollection RegisterRepository(this IServiceCollection services)
        {
            services.AddScoped(typeof(IRepository<>), typeof(BaseRepository<>));
            services.AddScoped(typeof(IRepositoryWorkflow<>), typeof(BaseRepositoryWorkflow<>));
            services.AddScoped(typeof(IRepositoryTextSearch<>), typeof(BaseRepositoryTextSearch<>));

            return services;
        }

        public static IServiceCollection RegisterMediatR(this IServiceCollection services, AppConfiguration appConfiguration)
        {
            services.AddScoped<Publisher>();
            services.AddMediatR(cfg =>
            {
                cfg.TypeEvaluator = d =>
                {
                    var attr = d.GetCustomAttribute(typeof(InjectionAttribute)) as InjectionAttribute;
                    if (attr is null) return true;
                    return attr.IsEnable(appConfiguration.AppScope,
                        appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType);

                    //if (!attr.Enable) return false;
                    //switch (attr.EventHandlerType)
                    //{
                    //    case EEventHandlerType.CallCenter:
                    //        return attr.Label == appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType;
                    //    default:
                    //        return attr.AppScopes.ToString()
                    //            .Split(',', StringSplitOptions.RemoveEmptyEntries)
                    //            .Any(x => string.Compare(x, appConfiguration.AppScope, StringComparison.Ordinal) == 0);
                    //}
                };
                //cfg.RegisterServicesFromAssemblyContaining<Program>();
                cfg.RegisterServicesFromAssembly(typeof(ApplicationStartupExtensions).Assembly);
            });

            return services;
        }

        public static IServiceCollection RegisterCompression(this IServiceCollection services)
        {
            services.Configure<BrotliCompressionProviderOptions>(options =>
            {
                options.Level = CompressionLevel.Optimal;
            }).Configure<GzipCompressionProviderOptions>(options =>
            {
                options.Level = CompressionLevel.Optimal;
            }).AddResponseCompression(options =>
            {
                options.EnableForHttps = true;
                options.Providers.Add<BrotliCompressionProvider>();
                options.Providers.Add<GzipCompressionProvider>();
                options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat([
                    "application/json; charset=utf-8",
                    "text/html; charset=utf-8",
                    "application/xhtml+xml",
                    "application/atom+xml",
                    "image/svg+xml"
                ]);
            });

            return services;
        }
    }
}
