using CMSMicroservice.Infrastructure.Persistence;
using CMSMicroservice.Infrastructure.Data.Seeding;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.WebApi.Hubs;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using System.Linq;
using Serilog.Core;
using Serilog;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.OpenApi.Models;
using CMSMicroservice.WebApi.Common.Behaviours;
using Hangfire;
using Hangfire.SqlServer;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using System.IO;

var builder = WebApplication.CreateBuilder(args);

// Configure Kestrel to support both HTTP/1.1 and HTTP/2
builder.WebHost.ConfigureKestrel(options =>
{
    // Enable both HTTP/1.1 and HTTP/2 for all endpoints
    options.ConfigureEndpointDefaults(listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
    });
});
var levelSwitch = new LoggingLevelSwitch();

// Read Seq configuration from appsettings.json
var seqServerUrl = builder.Configuration["Seq:ServerUrl"] ?? "http://seq-svc:5341";
var seqApiKey = builder.Configuration["Seq:ApiKey"]; 

var logger = new LoggerConfiguration()
     .WriteTo.Console()
     //.WriteTo.MSSqlServer(builder.Configuration.GetConnectionString("LogConnection"),
     //    sinkOptions: new MSSqlServerSinkOptions
     //    {
     //        TableName = "LogCMSEvents",
     //        SchemaName = "Log",
     //        AutoCreateSqlTable = true
     //    })
     .WriteTo.Seq(seqServerUrl,
                  apiKey: string.IsNullOrEmpty(seqApiKey) ? null : seqApiKey, 
                  controlLevelSwitch: levelSwitch)
    .CreateLogger();
builder.Logging.AddSerilog(logger);
#if DEBUG
Serilog.Debugging.SelfLog.Enable(msg => Console.WriteLine(msg));
#endif

// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682

// Add services to the container.

builder.Services.AddGrpc(options =>
{
    options.Interceptors.Add<LoggingBehaviour>();
    options.Interceptors.Add<PerformanceBehaviour>();
    options.Interceptors.Add<CMSMicroservice.WebApi.Interceptors.PermissionInterceptor>();
    options.Interceptors.Add<CMSMicroservice.WebApi.Interceptors.ImagePathResolverInterceptor>();
    //options.Interceptors.Add<ExceptionHandlingBehaviour>();
    options.EnableDetailedErrors = true;
    options.MaxReceiveMessageSize = 1000 * 1024 * 1024; // 1 GB
    options.MaxSendMessageSize = 1000 * 1024 * 1024; // 1 GB
}).AddJsonTranscoding();
builder.Services.AddApplicationServices();
builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddPresentationServices(builder.Configuration);
builder.Services.AddProtobufServices();

#region Configure Hangfire
builder.Services.AddHangfire(config => config
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    .UseSqlServerStorage(builder.Configuration["ConnectionStrings:DefaultConnection"]));
builder.Services.AddHangfireServer();
#endregion

#region Configure Health Checks
builder.Services.AddHealthChecks()
    .AddDbContextCheck<ApplicationDbContext>("database");
#endregion

// Add Controllers for REST APIs
builder.Services.AddControllers();

// HttpClient for FMS fallback image download
builder.Services.AddHttpClient("FMS", client =>
{
    client.Timeout = TimeSpan.FromSeconds(30);
    client.DefaultRequestHeaders.Add("User-Agent", "FourSat-CMS/1.0");
});

#region Configure Cors

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll",
        builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Grpc-Status",
            "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding", "validation-errors-text"));
});

#endregion
builder.Services.AddGrpcSwagger();
builder.Services.AddSwaggerGen(c =>
{
    // CMS Core Services Documentation
    c.SwaggerDoc("cms", new OpenApiInfo 
    { 
        Title = "FourSat CMS - Core Services", 
        Version = "v1",
        Description = "Core CMS microservice APIs - Internal business logic and data management",
        Contact = new OpenApiContact
        {
            Name = "FourSat Development Team",
            Email = "dev@foursat.com"
        }
    });

    // Admin API Documentation (BFF)
    c.SwaggerDoc("admin", new OpenApiInfo 
    { 
        Title = "FourSat CMS - Admin BFF", 
        Version = "v1",
        Description = "Admin Backend-for-Frontend API - User Management, Products, Commission, Network, Reports",
        Contact = new OpenApiContact
        {
            Name = "FourSat Development Team",
            Email = "dev@foursat.com"
        }
    });
    
    // Customer API Documentation (BFF)
    c.SwaggerDoc("customer", new OpenApiInfo 
    { 
        Title = "FourSat CMS - Customer BFF", 
        Version = "v1",
        Description = "Customer Backend-for-Frontend API - Profile, Shop, Commission, Network Statistics",
        Contact = new OpenApiContact
        {
            Name = "FourSat Development Team", 
            Email = "dev@foursat.com"
        }
    });

    // Unified API Documentation (All endpoints)
    c.SwaggerDoc("unified", new OpenApiInfo 
    { 
        Title = "FourSat CMS - Unified API", 
        Version = "v1",
        Description = "Complete API Documentation - All Core, Admin & Customer endpoints in one place"
    });

    c.CustomSchemaIds(type => type.ToString());
    
    // Resolve conflicting actions for Swagger
    c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
    
    // Include XML documentation for gRPC services
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    if (File.Exists(xmlPath))
    {
        c.IncludeXmlComments(xmlPath);
    }

    // Security Definition
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please insert JWT with Bearer into field (Format: Bearer {token})",
        Name = "Authorization", 
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });

    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });

    // Group endpoints by functionality
    c.TagActionsBy(api =>
    {
        var serviceName = api.ActionDescriptor.RouteValues?["controller"] ?? 
                         GetGrpcServiceName(api.RelativePath);
        
        // Define service categories for better organization
        return serviceName switch
        {
            var name when name.Contains("User") => new[] { "👤 User Management" },
            var name when name.Contains("Product") => new[] { "📦 Product Catalog" },
            var name when name.Contains("Category") => new[] { "🗂️ Categories" },
            var name when name.Contains("Commission") => new[] { "💰 Commission & Earnings" },
            var name when name.Contains("Network") => new[] { "🌐 Network & Binary Tree" },
            var name when name.Contains("Club") => new[] { "🏆 Club Management" },
            var name when name.Contains("Discount") => new[] { "🏷️ Discount Shop" },
            var name when name.Contains("Order") => new[] { "🛒 Orders & Shopping" },
            var name when name.Contains("Payment") => new[] { "💳 Payments & Transactions" },
            var name when name.Contains("Inventory") => new[] { "📋 Inventory Management" },
            var name when name.Contains("Health") => new[] { "🔧 System Health" },
            var name when name.Contains("Configuration") => new[] { "⚙️ Configuration" },
            var name when name.Contains("Admin") => new[] { "👑 Administration" },
            _ => new[] { $"📋 {serviceName}" }
        };
    });

    c.DocInclusionPredicate((docName, apiDesc) =>
    {
        // Get service name from both REST controllers and gRPC services
        var controllerName = apiDesc.ActionDescriptor.RouteValues?["controller"] ?? "";
        var grpcServiceName = GetGrpcServiceName(apiDesc.RelativePath);
        var serviceName = !string.IsNullOrEmpty(controllerName) ? controllerName : grpcServiceName;
        
        return docName switch
        {
            "cms" => IsCoreService(serviceName),
            "admin" => IsAdminService(serviceName),
            "customer" => IsCustomerService(serviceName), 
            "unified" => true, // Show all endpoints
            _ => true
        };
    });
});

// Helper functions for gRPC service name extraction and categorization
static string GetGrpcServiceName(string? relativePath)
{
    if (string.IsNullOrEmpty(relativePath)) return "";
    
    var segments = relativePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
    return segments.Length > 0 ? segments[0] : "";
}

static bool IsCoreService(string serviceName)
{
    var coreServices = new[]
    {
        "Health", "Configuration", "OtpToken", "Contract", "AppVersion"
    };
    
    return coreServices.Any(core => serviceName.Contains(core, StringComparison.OrdinalIgnoreCase));
}

static bool IsAdminService(string serviceName)
{
    var adminServices = new[]
    {
        "Admin", "Role", "UserRole", "ManualPayment", "Inventory", 
        "Tag", "ProductTag", "ProductGalleries", "ProductImages", 
        "DiscountOrder", "FactorDetails", "Products", "ProductCategory",
        "Category", "DiscountCategory", "DiscountProduct"
    };
    
    return adminServices.Any(admin => serviceName.Contains(admin, StringComparison.OrdinalIgnoreCase));
}

static bool IsCustomerService(string serviceName)
{
    var customerServices = new[]
    {
        "User", "UserAddress", "Commission", "NetworkMembership", 
        "ClubMembership", "UserOrder", "UserWallet", "UserCarts",
        "DiscountShoppingCart", "City", "Package", "Transactions",
        "UserContract", "UserWalletHistory"
    };
    
    return customerServices.Any(customer => serviceName.Contains(customer, StringComparison.OrdinalIgnoreCase)) &&
           !IsAdminService(serviceName); // Exclude admin services
}
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();

    // Initialise and seed database
    using (var scope = app.Services.CreateScope())
    {
        var initialiser = scope.ServiceProvider.GetRequiredService<ApplicationDbContextInitialiser>();
        await initialiser.InitialiseAsync();
        await initialiser.SeedAsync();
        
        // Run Migration: ParentId → NetworkParentId (فقط یکبار اجرا می‌شود)
        var migrationLogger = scope.ServiceProvider.GetRequiredService<ILogger<NetworkParentIdMigrationSeeder>>();
        var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        var migrationSeeder = new NetworkParentIdMigrationSeeder(dbContext, migrationLogger);
        await migrationSeeder.SeedAsync();
        
        // Seed WeekDefinitions (هفته‌ها)
        var weekSeederLogger = scope.ServiceProvider.GetRequiredService<ILogger<WeekDefinitionSeeder>>();
        var weekSeeder = new WeekDefinitionSeeder(dbContext, weekSeederLogger);
        await weekSeeder.SeedAsync();
    }
}
else
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

// Load WeekDefinition cache (برای هر دو محیط Development و Production)
var weekDefinitionRepository = app.Services.GetRequiredService<IWeekDefinitionRepository>();
await weekDefinitionRepository.ReloadCacheAsync();


app.UseRouting();
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseAuthorization();

// Enable static files for Swagger custom CSS
app.UseStaticFiles();

// Map Health Check endpoints
app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});
app.MapHealthChecks("/health/live", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
{
    Predicate = _ => false
});
app.MapControllers();
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); // Configure the HTTP request pipeline.

// Map SignalR Hub for token notifications
app.MapHub<TokenNotificationHub>("/hubs/token-notification");
app.MapHub<TokenNotificationHub>("/hubs/token-relay"); // Alias for FrontOffice backward compatibility

app.ConfigureGrpcEndpoints(Assembly.GetExecutingAssembly(), endpoints =>
{
    // endpoints.MapGrpcService<ExampleService>();
});

app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.UseSwagger();
app.UseSwaggerUI(c =>
{
    // CMS Core Services
    c.SwaggerEndpoint("/swagger/cms/swagger.json", "🔧 CMS Core Services");
    
    // Admin BFF
    c.SwaggerEndpoint("/swagger/admin/swagger.json", "👑 Admin BFF");
    
    // Customer BFF  
    c.SwaggerEndpoint("/swagger/customer/swagger.json", "👥 Customer BFF");
    
    // Unified API (All endpoints)
    c.SwaggerEndpoint("/swagger/unified/swagger.json", "🌐 Unified API (All)");

    // UI Customization
    c.DocumentTitle = "FourSat CMS API Documentation";
    c.RoutePrefix = "swagger"; // Available at /swagger
    
    // Default to CMS core view
    c.DefaultModelExpandDepth(2);
    c.DefaultModelsExpandDepth(-1);
    c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
    c.EnableFilter();
    c.EnableDeepLinking();
    c.EnableValidator();
    
    // Custom CSS
    c.InjectStylesheet("/swagger-ui/custom.css");
});

// Configure Hangfire Dashboard
app.UseHangfireDashboard("/hangfire", new Hangfire.DashboardOptions
{
    // TODO: برای production از Authorization filter استفاده کنید
    Authorization = Array.Empty<Hangfire.Dashboard.IDashboardAuthorizationFilter>()
});

// Configure Recurring Jobs
using (var scope = app.Services.CreateScope())
{
    var recurringJobManager = scope.ServiceProvider.GetRequiredService<IRecurringJobManager>();
    
    // Weekly Commission Calculation: Every Sunday at 00:05 (configurable)
    var weeklyCommissionEnabled = app.Configuration.GetValue<bool>("BackgroundJobs:WeeklyCommissionCalculation:Enabled", true);
    var weeklyCommissionCron = app.Configuration.GetValue<string>("BackgroundJobs:WeeklyCommissionCalculation:CronExpression", "5 0 * * 0");
    
    if (weeklyCommissionEnabled)
    {
        recurringJobManager.AddOrUpdate<CMSMicroservice.Infrastructure.BackgroundJobs.WeeklyCommissionJob>(
            recurringJobId: "weekly-commission-calculation",
            methodCall: job => job.ExecuteAsync(null, CancellationToken.None),
            cronExpression: weeklyCommissionCron,
            options: new RecurringJobOptions
            {
                TimeZone = TimeZoneInfo.Local
            });
        
        app.Logger.LogInformation("✅ Hangfire recurring job 'weekly-commission-calculation' registered (Cron: {Cron})", weeklyCommissionCron);
    }
    else
    {
        recurringJobManager.RemoveIfExists("weekly-commission-calculation");
        app.Logger.LogInformation("⚠️ Hangfire recurring job 'weekly-commission-calculation' is DISABLED in configuration");
    }
    
    // Daya Loan Check: Every 15 minutes
    CMSMicroservice.WebApi.Workers.DayaLoanCheckWorker.Schedule(recurringJobManager);
    app.Logger.LogInformation("✅ Hangfire recurring job 'daya-loan-check' registered (Cron: */15 * * * * - Every 15 minutes)");
    
    // Chatika Account Activation: Every 5 minutes
    recurringJobManager.AddOrUpdate<CMSMicroservice.Infrastructure.BackgroundJobs.ChatikaAccountActivationJob>(
        recurringJobId: "chatika-account-activation",
        methodCall: job => job.ExecuteAsync(CancellationToken.None),
        cronExpression: "*/5 * * * *",
        options: new RecurringJobOptions { TimeZone = TimeZoneInfo.Local });
    app.Logger.LogInformation("✅ Hangfire recurring job 'chatika-account-activation' registered (Cron: */5 * * * * - Every 5 minutes)");

    // Zarinpal Payment Reconciliation: Every 30 minutes
    // Verifies payments older than 30 min whose return-URL callback was never received
    var zarinpalReconciliationEnabled = app.Configuration.GetValue<bool>(
        "BackgroundJobs:ZarinpalReconciliation:Enabled", true);
    if (zarinpalReconciliationEnabled)
    {
        recurringJobManager.AddOrUpdate<CMSMicroservice.Infrastructure.BackgroundJobs.ZarinpalReconciliationJob>(
            recurringJobId: "zarinpal-reconciliation",
            methodCall: job => job.ExecuteAsync(CancellationToken.None),
            cronExpression: "*/30 * * * *",
            options: new RecurringJobOptions { TimeZone = TimeZoneInfo.Local });
        app.Logger.LogInformation("✅ Hangfire recurring job 'zarinpal-reconciliation' registered (Cron: */30 * * * * - Every 30 minutes)");
    }
    else
    {
        recurringJobManager.RemoveIfExists("zarinpal-reconciliation");
        app.Logger.LogInformation("⚠️ Hangfire recurring job 'zarinpal-reconciliation' is DISABLED in configuration");
    }
}

app.Run();
