using System.Security.Claims;
using System.Text;
using System.Text.Json.Serialization;
using HealthChecks.UI.Client;
using Managing.Api.Authorization;
using Managing.Api.Filters;
using Managing.Api.HealthChecks;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Bootstrap;
using Managing.Common;
using Managing.Core.Middleawares;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Databases.PostgreSql.Configurations;
using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using NSwag;
using NSwag.Generation.Processors.Security;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.Elasticsearch;
using OpenApiSecurityRequirement = Microsoft.OpenApi.Models.OpenApiSecurityRequirement;
using OpenApiSecurityScheme = NSwag.OpenApiSecurityScheme;

// Builder
var builder = WebApplication.CreateBuilder(args);

builder.Configuration.SetBasePath(AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json")
    .AddJsonFile($"config.{builder.Environment.EnvironmentName}.json",
        optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddUserSecrets<Program>();

SentrySdk.Init(options =>
{
    // A Sentry Data Source Name (DSN) is required.
    // See https://docs.sentry.io/concepts/key-terms/dsn-explainer/
    // You can set it in the SENTRY_DSN environment variable, or you can set it in code here.
    options.Dsn = builder.Configuration["Sentry:Dsn"];

    // When debug is enabled, the Sentry client will emit detailed debugging information to the console.
    // This might be helpful, or might interfere with the normal operation of your application.
    // We enable it here for demonstration purposes when first trying Sentry.
    // You shouldn't do this in your applications unless you're troubleshooting issues with Sentry.
    options.Debug = false;

    // Adds request URL and headers, IP and name for users, etc.
    options.SendDefaultPii = true;

    // This option is recommended. It enables Sentry's "Release Health" feature.
    options.AutoSessionTracking = true;

    // Enabling this option is recommended for client applications only. It ensures all threads use the same global scope.
    options.IsGlobalModeEnabled = false;

    // Example sample rate for your transactions: captures 10% of transactions
    options.TracesSampleRate = 0.1;

    options.Environment = builder.Environment.EnvironmentName;
});

// Add Service Defaults - using extension methods directly
builder.Services.AddServiceDiscovery();
builder.Services.AddHealthChecks()
    .AddCheck("self", () => HealthCheckResult.Healthy(), ["api"]);

var postgreSqlConnectionString = builder.Configuration.GetSection(Constants.Databases.PostgreSql)["ConnectionString"];
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];

// Add HTTP client for Web3Proxy health check with detailed response
builder.Services.AddHttpClient("Web3ProxyHealthCheck")
    .ConfigureHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(15); });

// Add HTTP client for GMX API health check
builder.Services.AddHttpClient("GmxHealthCheck")
    .ConfigureHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(10); });

// Register Web3ProxyHealthCheck with the web3ProxyUrl
builder.Services.AddSingleton<Web3ProxyHealthCheck>(sp =>
    new Web3ProxyHealthCheck(sp.GetRequiredService<IHttpClientFactory>(), web3ProxyUrl));

// Add PostgreSQL DbContext with improved concurrency and connection management
builder.Services.AddDbContext<ManagingDbContext>(options =>
{
    options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions =>
    {
        // Configure connection pooling and timeout settings for better concurrency
        npgsqlOptions.CommandTimeout(60); // Increase command timeout for complex queries
        npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10),
            errorCodesToAdd: null);
    });

    // Enable detailed errors in development
    if (builder.Environment.IsDevelopment())
    {
        options.EnableDetailedErrors();
        options.EnableSensitiveDataLogging();
        options.EnableThreadSafetyChecks(); // Enable thread safety checks in development
    }

    // Configure query tracking behavior for better performance
    options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); // Default to no tracking for better performance

    // Enable service provider caching for better performance
    options.EnableServiceProviderCaching();

    // Enable connection resiliency for backtest and high-load scenarios
    options.LogTo(msg => Console.WriteLine(msg), LogLevel.Warning); // Log warnings for connection issues
}, ServiceLifetime.Scoped); // Explicitly specify scoped lifetime for proper request isolation

// Add specific health checks for databases and other services
builder.Services.AddHealthChecks()
    .AddNpgSql(postgreSqlConnectionString, name: "postgresql", tags: ["database"])
    .AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
    .AddCheck<Web3ProxyHealthCheck>("web3proxy", tags: ["api", "external"])
    .AddCheck<CandleDataHealthCheck>("candle-data", tags: ["database", "candles"])
    .AddCheck<GmxConnectivityHealthCheck>("gmx-connectivity", tags: ["api", "external"]);

builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
{
    var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-");
    var indexFormat = $"managing-{envName}-" + "{0:yyyy.MM.dd}";
    var yourTemplateName = "dotnetlogs";
    var es = new ElasticsearchSinkOptions(new Uri(hostBuilder.Configuration["ElasticConfiguration:Uri"]))
    {
        IndexFormat = indexFormat.ToLower(),
        AutoRegisterTemplate = true,
        OverwriteTemplate = true,
        TemplateName = yourTemplateName,
        AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
        TypeName = null,
        BatchAction = ElasticOpType.Create,
        MinimumLogEventLevel = LogEventLevel.Information,
        DetectElasticsearchVersion = true,
        RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
    };

    loggerConfiguration
        .WriteTo.Console()
        .WriteTo.Elasticsearch(es);
});

builder.Services.AddOptions();
builder.Services.Configure<PostgreSqlSettings>(builder.Configuration.GetSection(Constants.Databases.PostgreSql));
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
builder.Services.AddControllers().AddJsonOptions(options =>
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(o =>
    {
        o.SaveToken = true;
        o.TokenValidationParameters = new TokenValidationParameters
        {
            ValidIssuer = builder.Configuration["Authentication:Schemes:Bearer:ValidIssuer"],
            ValidAudience = builder.Configuration["Authentication:Schemes:Bearer:ValidAudiences"],
            IssuerSigningKey = new SymmetricSecurityKey
                (Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"])),
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateIssuerSigningKey = true
        };
        o.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // If you want to get the token from a custom header or query string
                // var accessToken = context.Request.Query["access_token"];
                // if (!string.IsNullOrEmpty(accessToken) &&
                //     context.HttpContext.Request.Path.StartsWithSegments("/hub"))
                // {
                //     context.Token = accessToken;
                // }
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = context =>
            {
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers.Add("Token-Expired", "true");
                }

                return Task.CompletedTask;
            },
            // --- IMPORTANT: Attach User to Context Here ---
            OnTokenValidated = async context =>
            {
                var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
                // Assuming your JWT token contains a 'nameid' claim (or similar) for the user ID
                var userId = context.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;

                if (!string.IsNullOrEmpty(userId))
                {
                    // Fetch the full user object from your service
                    var user = await userService.GetUserByAddressAsync(userId);
                    if (user != null)
                    {
                        // Attach the user object to HttpContext.Items
                        context.HttpContext.Items["User"] = user;
                    }
                }

                await Task.CompletedTask;
            }
            // --- END IMPORTANT ---
        };
    });

builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
    builder
        .SetIsOriginAllowed((host) => true)
        .AllowAnyOrigin()
        .WithOrigins("http://localhost:3000/")
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials();
}));

builder.Services.AddSignalR().AddJsonProtocol();
builder.Services.AddScoped<IJwtUtils, JwtUtils>();

builder.Services.RegisterApiDependencies(builder.Configuration);

// Orleans is always configured, but grains can be controlled
builder.Host.ConfigureOrleans(builder.Configuration, builder.Environment.IsProduction());

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(document =>
{
    document.AddSecurity("JWT", Enumerable.Empty<string>(), new OpenApiSecurityScheme
    {
        Type = OpenApiSecuritySchemeType.ApiKey,
        Name = "Authorization",
        In = OpenApiSecurityApiKeyLocation.Header,
        Description = "Type into the textbox: Bearer {your JWT token}."
    });

    document.OperationProcessors.Add(
        new AspNetCoreOperationSecurityScopeProcessor("JWT"));
});
builder.Services.AddSwaggerGen(options =>
{
    options.SchemaFilter<EnumSchemaFilter>();
    options.AddSecurityDefinition("Bearer,", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
    {
        Description = "Please insert your JWT Token into field : Bearer {your_token}",
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        In = ParameterLocation.Header,
        Scheme = "Bearer",
        BearerFormat = "JWT"
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new Microsoft.OpenApi.Models.OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });
});

builder.WebHost.SetupDiscordBot();

// App
var app = builder.Build();
app.UseSerilogRequestLogging();
app.UseOpenApi();
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Managing API v1");
    c.RoutePrefix = string.Empty;
});

app.UseCors("CorsPolicy");

// Add Sentry diagnostics middleware (now using shared version)
app.UseSentryDiagnostics();

// Using shared GlobalErrorHandlingMiddleware from core project
app.UseMiddleware<GlobalErrorHandlingMiddleware>();

app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapHub<BotHub>("/bothub");
    endpoints.MapHub<BacktestHub>("/backtesthub");
    endpoints.MapHub<CandleHub>("/candlehub");

    endpoints.MapHealthChecks("/health", new HealthCheckOptions
    {
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });

    endpoints.MapHealthChecks("/alive", new HealthCheckOptions
    {
        Predicate = r => r.Tags.Contains("live"),
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });
});

// Conditionally run the application based on deployment mode
var deploymentMode = builder.Configuration.GetValue<bool>("DeploymentMode", false);

if (!deploymentMode)
{
    Console.WriteLine("Application starting in normal mode...");
    app.Run();
}
else
{
    Console.WriteLine("Application configured for deployment mode - skipping app.Run()");
    Console.WriteLine("All services have been configured and the application is ready for deployment.");
}