using Dapper;
using DexDemoBackend;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
using Npgsql;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

// Logging configuration is now handled in appsettings.json
// Only add console logging if not already configured
if (!builder.Logging.Services.Any(s => s.ServiceType == typeof(ILoggerProvider)))
{
    builder.Logging.AddConsole();
}

// Logger will be created after app is built

// // Force binding to all interfaces in Kubernetes
// Console.WriteLine($"ASPNETCORE_URLS env var: {Environment.GetEnvironmentVariable("ASPNETCORE_URLS")}");
// Console.WriteLine($"ASPNETCORE_HTTP_PORTS env var: {Environment.GetEnvironmentVariable("ASPNETCORE_HTTP_PORTS")}");

// // Clear any existing configuration and force our URL
// builder.WebHost.ConfigureKestrel(options =>
// {
//     options.ListenAnyIP(8000);
// });

// Configuration
var config = new AppConfig
{
    DbHost = Environment.GetEnvironmentVariable("DB_HOST") ?? "postgres",
    DbPort = Environment.GetEnvironmentVariable("DB_PORT") ?? "5440",
    DbName = Environment.GetEnvironmentVariable("DB_NAME") ?? "dexdemo",
    DbUser = Environment.GetEnvironmentVariable("DB_USER") ?? "dexdemo",
    DbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD") ?? "dexdemo",
    Issuer = Environment.GetEnvironmentVariable("DEX_ISSUER") ?? "https://dex.127.0.0.1.sslip.io/",
    InsecureDevMode = Environment.GetEnvironmentVariable("INSECURE_DEV_MODE")?.ToLower() == "true",
    InsecureDevEmail = Environment.GetEnvironmentVariable("INSECURE_DEV_EMAIL")
};

// Configuration will be logged after logger is created

builder.Services.AddSingleton(config);

// Configure JWT Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = config.Issuer;
        options.RequireHttpsMetadata = false; // For development
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = config.Issuer,
            ValidateAudience = false, // Можно включить, если DEX выдает audience
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.FromMinutes(5), // Допуск 5 минут на разницу времени
            
            // Настройка для заполнения User.Identity.Name
            NameClaimType = "name", // Используем claim "name" для User.Identity.Name
            RoleClaimType = "role"  // Используем claim "role" для ролей
        };
        
        // Add detailed event logging for JWT authentication
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>();
                jwtLogger.LogError(context.Exception, "[JWT Auth Failed] Exception: {Message}", context.Exception?.Message);
                jwtLogger.LogDebug("[JWT Auth Failed] StackTrace: {StackTrace}", context.Exception?.StackTrace);
                return Task.CompletedTask;
            },
            OnTokenValidated = context =>
            {
                var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>();
                
                // Логируем все доступные claims для отладки
                var claims = string.Join(", ", context.Principal?.Claims?.Select(c => $"{c.Type}={c.Value}") ?? new string[0]);
                jwtLogger.LogDebug("[JWT Token Validated] All claims: {Claims}", claims);
                
                // Проверяем, есть ли name claim, если нет - используем email как fallback
                var nameClaim = context.Principal?.FindFirst("name");
                var emailClaim = context.Principal?.FindFirst("email");
                var subClaim = context.Principal?.FindFirst("sub");
                
                if (nameClaim == null && emailClaim != null)
                {
                    // Если нет name claim, но есть email - добавляем его как name claim
                    var identity = context.Principal?.Identity as ClaimsIdentity;
                    identity?.AddClaim(new Claim("name", emailClaim.Value));
                    jwtLogger.LogDebug("[JWT Token Validated] Added email as name claim: {Email}", emailClaim.Value);
                }
                
                jwtLogger.LogInformation("[JWT Token Validated] User.Identity.Name: {UserName}", context.Principal?.Identity?.Name);
                jwtLogger.LogInformation("[JWT Token Validated] Email claim: {Email}", emailClaim?.Value ?? "Not found");
                jwtLogger.LogInformation("[JWT Token Validated] Sub claim: {Sub}", subClaim?.Value ?? "Not found");
                
                return Task.CompletedTask;
            },
            OnMessageReceived = context =>
            {
                var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>();
                jwtLogger.LogDebug("[JWT Message Received] Scheme: {Scheme}, Path: {Path}, Method: {Method}", 
                    context.Scheme.Name, context.Request.Path, context.Request.Method);
                
                // Log Authorization header (without sensitive token data)
                if (context.Request.Headers.TryGetValue("Authorization", out var authHeader))
                {
                    var authValue = authHeader.ToString();
                    if (authValue.StartsWith("Bearer "))
                    {
                        var tokenPreview = authValue.Substring(7, Math.Min(20, authValue.Length - 7)) + "...";
                        jwtLogger.LogDebug("[JWT Message Received] Token preview: {TokenPreview}", tokenPreview);
                    }
                }
                
                return Task.CompletedTask;
            },
            OnChallenge = context =>
            {
                var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>();
                jwtLogger.LogWarning("[JWT Challenge] Error: {Error}, ErrorDescription: {ErrorDescription}, ErrorUri: {ErrorUri}", 
                    context.Error, context.ErrorDescription, context.ErrorUri);
                return Task.CompletedTask;
            }
        };
        
        // Configure OpenID Connect discovery
        var httpClientHandler = new HttpClientHandler();
        
        // В режиме разработки отключаем проверку SSL
        // В продакшене это должно быть настроено правильно!
        if (config.InsecureDevMode)
        {
            // Logging will be done after logger is created
            httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
        }
        
        var discoveryUrl = $"{config.Issuer}.well-known/openid-configuration";
        // Logging will be done after logger is created
        
        options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            discoveryUrl,
            new OpenIdConnectConfigurationRetriever(),
            new HttpDocumentRetriever(new HttpClient(httpClientHandler)) 
            { 
                RequireHttps = !config.InsecureDevMode 
            }
        );
    });

builder.Services.AddAuthorization();

// Configure JSON serialization to use snake_case
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower;
});

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.AllowAnyOrigin()
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

var app = builder.Build();
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

// Create logger after app is built
var logger = app.Services.GetRequiredService<ILogger<Program>>();

// Log configuration (without sensitive data)
logger.LogInformation("[Config] Application configuration:");
logger.LogInformation("[Config]   DB_HOST: {DbHost}", config.DbHost);
logger.LogInformation("[Config]   DB_PORT: {DbPort}", config.DbPort);
logger.LogInformation("[Config]   DB_NAME: {DbName}", config.DbName);
logger.LogInformation("[Config]   DB_USER: {DbUser}", config.DbUser);
logger.LogInformation("[Config]   DB_PASSWORD: [HIDDEN]");
logger.LogInformation("[Config]   DEX_ISSUER: {Issuer}", config.Issuer);
logger.LogInformation("[Config]   INSECURE_DEV_MODE: {InsecureDevMode}", config.InsecureDevMode);
logger.LogInformation("[Config]   INSECURE_DEV_EMAIL: {InsecureDevEmail}", config.InsecureDevEmail ?? "[NOT SET]");

// Log JWT configuration
if (config.InsecureDevMode)
{
    logger.LogWarning("[JWT Config] InsecureDevMode enabled - disabling SSL certificate validation");
}
logger.LogInformation("[JWT Config] OpenID Connect discovery URL: {DiscoveryUrl}", $"{config.Issuer}.well-known/openid-configuration");
logger.LogInformation("[JWT Config] RequireHttps: {RequireHttps}", !config.InsecureDevMode);

app.UseCors();
app.UseAuthentication();
app.UseAuthorization();

logger.LogInformation("[App] Application middleware configured:");
logger.LogInformation("[App]   CORS: Enabled");
logger.LogInformation("[App]   Authentication: JWT Bearer");
logger.LogInformation("[App]   Authorization: Enabled");

// Health check endpoint
app.MapGet("/api/health", () => Results.Ok(new { status = "ok" }));

// User identity demo endpoint
app.MapGet("/api/user-identity", [Authorize] (HttpContext context, [FromServices] ILogger<Program> logger) =>
{
    logger.LogInformation("[UserIdentity] User.Identity.Name: {Name}", context.User.Identity?.Name ?? "NULL");
    logger.LogInformation("[UserIdentity] User.Identity.IsAuthenticated: {IsAuthenticated}", context.User.Identity?.IsAuthenticated);
    logger.LogInformation("[UserIdentity] User.Identity.AuthenticationType: {AuthType}", context.User.Identity?.AuthenticationType);
    
    return Results.Ok(new 
    { 
        name = context.User.Identity?.Name,
        isAuthenticated = context.User.Identity?.IsAuthenticated,
        authenticationType = context.User.Identity?.AuthenticationType,
        claims = context.User.Claims.Select(c => new { type = c.Type, value = c.Value }).ToList()
    });
});

// User info endpoint
app.MapGet("/api/user-info", [Authorize] async (HttpContext context, [FromServices] AppConfig cfg, [FromServices] ILogger<Program> userInfoLogger) =>
{
    userInfoLogger.LogInformation("[UserInfo] Starting user info request for path: {Path}", context.Request.Path);
    userInfoLogger.LogInformation("[UserInfo] User authenticated: {IsAuthenticated}", context.User.Identity?.IsAuthenticated);
    
    try
    {
        var email = await GetUserEmail(context, cfg, userInfoLogger);
        userInfoLogger.LogInformation("[UserInfo] Retrieved email: {Email}", email);
        
        await using var conn = new NpgsqlConnection(cfg.ConnectionString);
        await conn.OpenAsync();
        userInfoLogger.LogDebug("[UserInfo] Database connection opened successfully");

        // Get user info
        userInfoLogger.LogDebug("[UserInfo] Querying user info for email: {Email}", email);
        var userResult = await conn.QuerySingleOrDefaultAsync<UserQueryResult>(@"
            SELECT u.email, u.full_name, u.organization_id, o.name as org_name
            FROM users u
            LEFT JOIN organizations o ON u.organization_id = o.id
            WHERE u.email = @email", 
            new { email });
        
        if (userResult is null)
        {
            userInfoLogger.LogWarning("[UserInfo] User not found in database for email: {Email}", email);
            return Results.NotFound(new { detail = "User not found in database" });
        }

        userInfoLogger.LogInformation("[UserInfo] User found: {Email}, FullName: {FullName}, OrgId: {OrgId}", 
            userResult.Email, userResult.FullName, userResult.OrganizationId);

        var organization = userResult.OrganizationId.HasValue && userResult.OrgName != null 
            ? new Organization(userResult.OrganizationId.Value, userResult.OrgName) 
            : null;

        // Get user roles
        userInfoLogger.LogDebug("[UserInfo] Querying user roles for email: {Email}", email);
        var roles = (await conn.QueryAsync<Role>(@"
            SELECT r.id, r.name, r.description
            FROM roles r
            JOIN user_roles ur ON r.id = ur.role_id
            JOIN users u ON ur.user_id = u.id
            WHERE u.email = @email",
            new { email })).ToList();

        userInfoLogger.LogInformation("[UserInfo] Found {RoleCount} roles for user", roles.Count);

        // Get available links
        userInfoLogger.LogDebug("[UserInfo] Querying available links for email: {Email}", email);
        var links = (await conn.QueryAsync<Link>(@"
            SELECT DISTINCT l.id, l.title, l.url, l.description
            FROM links l
            JOIN role_links rl ON l.id = rl.link_id
            JOIN user_roles ur ON rl.role_id = ur.role_id
            JOIN users u ON ur.user_id = u.id
            WHERE u.email = @email
            ORDER BY l.id",
            new { email })).ToList();

        userInfoLogger.LogInformation("[UserInfo] Found {LinkCount} links for user", links.Count);
        userInfoLogger.LogInformation("[UserInfo] Returning user info successfully");

        return Results.Ok(new UserInfo(userResult.Email, userResult.FullName, organization, roles, links));
    }
    catch (Exception ex)
    {
        userInfoLogger.LogError(ex, "[UserInfo] Error occurred: {Message}", ex.Message);
        throw;
    }
});

logger.LogInformation("[App] Application starting...");
logger.LogInformation("[App] Available endpoints:");
logger.LogInformation("[App]   GET /api/health - Health check");
logger.LogInformation("[App]   GET /api/user-identity - User identity information (requires authentication)");
logger.LogInformation("[App]   GET /api/user-info - User information (requires authentication)");

app.Run();

static Task<string> GetUserEmail(HttpContext context, AppConfig config, ILogger logger)
{
    logger.LogDebug("[GetUserEmail] Starting authentication check for path: {Path}", context.Request.Path);
    logger.LogDebug("[GetUserEmail] Request method: {Method}", context.Request.Method);
    logger.LogDebug("[GetUserEmail] User.Identity.IsAuthenticated: {IsAuthenticated}", context.User.Identity?.IsAuthenticated);
    logger.LogInformation("[GetUserEmail] User.Identity.Name: {Name}", context.User.Identity?.Name ?? "NULL");
    logger.LogDebug("[GetUserEmail] User.Identity.AuthenticationType: {AuthType}", context.User.Identity?.AuthenticationType);
    
    // Log all claims
    if (context.User.Identity?.IsAuthenticated == true)
    {
        logger.LogDebug("[GetUserEmail] All user claims:");
        foreach (var claim in context.User.Claims)
        {
            logger.LogDebug("[GetUserEmail]   {ClaimType} = {ClaimValue}", claim.Type, claim.Value);
        }
    }
    
    // Log all headers
    logger.LogDebug("[GetUserEmail] Request headers:");
    foreach (var header in context.Request.Headers)
    {
        if (header.Key.ToLower().Contains("auth") || header.Key.ToLower().Contains("email"))
        {
            logger.LogDebug("[GetUserEmail]   {HeaderKey} = {HeaderValue}", header.Key, header.Value);
        }
    }

    // Dev mode
    if (config.InsecureDevMode)
    {
        logger.LogInformation("[GetUserEmail] INSECURE_DEV_MODE: Using email {Email}", config.InsecureDevEmail);
        return Task.FromResult(config.InsecureDevEmail!);
    }

    // Try JWT token from authenticated user
    if (context.User.Identity?.IsAuthenticated == true)
    {
        logger.LogDebug("[GetUserEmail] Checking JWT token claims for email");
        var emailClaim = context.User.FindFirst("email");
        if (emailClaim != null)
        {
            logger.LogInformation("[GetUserEmail] Found email in JWT claim: {Email}", emailClaim.Value);
            return Task.FromResult(emailClaim.Value);
        }
        else
        {
            logger.LogWarning("[GetUserEmail] No email claim found in JWT token");
        }
    }
    else
    {
        logger.LogDebug("[GetUserEmail] User is not authenticated via JWT");
    }

    // Try OAuth2 proxy header
    logger.LogDebug("[GetUserEmail] Checking OAuth2 proxy headers");
    if (context.Request.Headers.TryGetValue("X-Auth-Request-Email", out var emailHeader))
    {
        logger.LogInformation("[GetUserEmail] Found email in OAuth2 proxy header: {Email}", emailHeader.ToString());
        return Task.FromResult(emailHeader.ToString());
    }
    else
    {
        logger.LogDebug("[GetUserEmail] No X-Auth-Request-Email header found");
    }

    logger.LogError("[GetUserEmail] No authentication information found - throwing UnauthorizedAccessException");
    throw new UnauthorizedAccessException("No authentication information found");
}

// Internal query result model
record UserQueryResult(string Email, string FullName, int? OrganizationId, string? OrgName);