using System.Security.Claims;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Persistence.Models.Configurations;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace Persistence.API;

public static class DependencyInjection
{
    public static void AddSwagger(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSwaggerGen(c =>
        {
            c.MapType<TimeSpan>(() => new OpenApiSchema { Type = "string", Example = new OpenApiString("0.00:00:00") });
            c.MapType<DateOnly>(() => new OpenApiSchema { Type = "string", Format = "date" });
            c.MapType<JsonValue>(() => new OpenApiSchema
            {
                AnyOf = new OpenApiSchema[]
                {
                    new OpenApiSchema {Type = "string", Format = "string" },
                    new OpenApiSchema {Type = "number", Format = "int32" },
                    new OpenApiSchema {Type = "number", Format = "float" },
                }
            });

            c.CustomOperationIds(e =>
            {
                return $"{e.ActionDescriptor.RouteValues["action"]}";
            });

            c.SwaggerDoc("v1", new OpenApiInfo { Title = "Persistence web api", Version = "v1" });

			var needUseKeyCloak = configuration.GetSection("NeedUseKeyCloak").Get<bool>();
			if (needUseKeyCloak) 
				c.AddKeycloackSecurity(configuration);
			else c.AddDefaultSecurity(configuration);

			//var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
			//var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
			//var includeControllerXmlComment = true;
			//options.IncludeXmlComments(xmlPath, includeControllerXmlComment);
			//options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "AsbCloudApp.xml"), includeControllerXmlComment);
		});
    }

	#region Authentication
	public static void AddJWTAuthentication(this IServiceCollection services, IConfiguration configuration)
    {
        var needUseKeyCloak = configuration
			.GetSection("NeedUseKeyCloak")
			.Get<bool>();
		if (needUseKeyCloak) 
			services.AddKeyCloakAuthentication(configuration);
		else services.AddDefaultAuthentication(configuration);
	}

	private static void AddKeyCloakAuthentication(this IServiceCollection services, IConfiguration configuration)
	{
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
			.AddJwtBearer(options =>
			{
				options.RequireHttpsMetadata = false;
				options.Audience = configuration["Authentication:Audience"];
				options.MetadataAddress = configuration["Authentication:MetadataAddress"]!;
				options.TokenValidationParameters = new TokenValidationParameters
				{
					ValidIssuer = configuration["Authentication:ValidIssuer"],
				};
            });
	}

	private static void AddDefaultAuthentication(this IServiceCollection services, IConfiguration configuration)
	{
		services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
			.AddJwtBearer(options =>
			{
				options.RequireHttpsMetadata = false;
				options.TokenValidationParameters = new TokenValidationParameters
				{
					ValidateIssuer = true,
					ValidIssuer = JwtParams.Issuer,
					ValidateAudience = true,
					ValidAudience = JwtParams.Audience,
					ValidateLifetime = true,
					IssuerSigningKey = JwtParams.SecurityKey,
					ValidateIssuerSigningKey = false,
				};
				options.Events = new JwtBearerEvents
				{
					OnMessageReceived = context =>
					{
						var accessToken = context.Request.Headers["Authorization"]
							.ToString()
							.Replace(JwtBearerDefaults.AuthenticationScheme, string.Empty)
							.Trim();

						context.Token = accessToken;

						return Task.CompletedTask;
					},
					OnTokenValidated = context =>
					{
						if(context.Principal != null && context.Principal.HasClaim(x => x.Type != ClaimTypes.NameIdentifier))
						{
							var claim = new Claim(ClaimTypes.NameIdentifier.ToString(), Guid.NewGuid().ToString());
                            (context.Principal.Identity as ClaimsIdentity)!.AddClaim(claim);
                        }

                        var username = context.Principal?.Claims
							.FirstOrDefault(e => e.Type == "username")?.Value;

						var password = context.Principal?.Claims
							.FirstOrDefault(e => e.Type == "password")?.Value;

						var keyCloakUser = configuration
							.GetSection(nameof(AuthUser))
							.Get<AuthUser>()!;

						if (username != keyCloakUser.Username || password != keyCloakUser.Password)
						{
							context.Fail("username or password did not match");
						}

						return Task.CompletedTask;
					}
				};
			});
	}
	#endregion

	#region Security (Swagger)
	private static void AddKeycloackSecurity(this SwaggerGenOptions options, IConfiguration configuration)
	{
		options.AddSecurityDefinition("Keycloack", new OpenApiSecurityScheme
		{
			Description = @"JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below. Example: 'Bearer 12345abcdef'",
			Name = "Authorization",
			In = ParameterLocation.Header,
			Type = SecuritySchemeType.OAuth2,
			Flows = new OpenApiOAuthFlows
			{
				Implicit = new OpenApiOAuthFlow
				{
					AuthorizationUrl = new Uri(configuration["Authentication:AuthorizationUrl"]),
				}
			}
		});

		options.AddSecurityRequirement(new OpenApiSecurityRequirement()
		{
			{
				new OpenApiSecurityScheme
				{
					Reference = new OpenApiReference
					{
						Type = ReferenceType.SecurityScheme,
						Id = "Keycloack"
					},
					Scheme = "Bearer",
					Name = "Bearer",
					In = ParameterLocation.Header,
				},
				new List<string>()
			}
		});
	}

	private static void AddDefaultSecurity(this SwaggerGenOptions options, IConfiguration configuration)
	{
		options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
		{
			Description = @"JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below. Example: 'Bearer 12345abcdef'",
			Name = "Authorization",
			In = ParameterLocation.Header,
			Type = SecuritySchemeType.ApiKey,
			Scheme = "Bearer",
		});

		options.AddSecurityRequirement(new OpenApiSecurityRequirement()
		{
			{
				new OpenApiSecurityScheme
				{
					Reference = new OpenApiReference
					{
						Type = ReferenceType.SecurityScheme,
						Id = "Bearer"
					},
					Scheme = "oauth2",
					Name = "Bearer",
					In = ParameterLocation.Header,
				},
				new List<string>()
			}
		});
	}
	#endregion
}