using System.Text;
using System.Text.Json.Serialization;
using FluentValidation;
using Hangfire;
using Hangfire.MemoryStorage;
using Hangfire.PostgreSql;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Meezi.API.Hubs;
using Meezi.API.Configuration;
using Meezi.API.Jobs;
using Meezi.API.Services;
using Meezi.API.Services.Delivery;
using Meezi.Infrastructure.Services.Platform;
using Meezi.API.Services.Printing;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure;
using Serilog;
using StackExchange.Redis;

namespace Meezi.API.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMeeziServices(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddHttpContextAccessor();
        services.AddMeeziSecurity(configuration);
        services.AddInfrastructure(configuration);
        services.AddScoped<IAuthService, AuthService>();
        services.AddScoped<IAuditLogService, AuditLogService>();
        services.AddScoped<IConsumerAuthService, ConsumerAuthService>();
        services.AddScoped<IConsumerOrdersService, ConsumerOrdersService>();
        services.AddScoped<IKitchenStationService, KitchenStationService>();
        services.AddScoped<INotificationInboxService, NotificationInboxService>();
        services.AddScoped<IOrderNotificationService, OrderNotificationService>();
        services.AddScoped<IWebsiteService, Meezi.Infrastructure.Services.WebsiteService>();
        services.AddScoped<IJwtTokenService, JwtTokenService>();
        services.AddSingleton<IRefreshTokenStore, RedisRefreshTokenStore>();
        services.AddScoped<IPlanLimitChecker, PlanLimitChecker>();
        services.AddScoped<IMenuService, MenuService>();
        services.AddScoped<IBranchMenuService, BranchMenuService>();
        services.AddScoped<IBranchIdentityService, BranchIdentityService>();
        services.AddScoped<ITableService, TableService>();
        services.AddScoped<IOrderService, OrderService>();
        services.AddScoped<IKdsNotifier, KdsNotifier>();
        services.AddScoped<ICustomerService, CustomerService>();
        services.AddScoped<ICouponService, CouponService>();
        services.AddScoped<ITaxService, TaxService>();
        services.AddSingleton<IMediaStorageService, MediaStorageService>();
        services.Configure<MenuAi3dOptions>(configuration.GetSection(MenuAi3dOptions.SectionName));
        services.AddHttpClient("MenuAi3d");
        services.AddHttpClient("OpenAi");
        services.AddScoped<IOpenAiChatService, OpenAiChatService>();
        services.AddScoped<ICoffeeAdvisorService, CoffeeAdvisorService>();
        services.AddScoped<IMenuAi3dGenerationService, MenuAi3dGenerationService>();
        services.AddScoped<IBranchLifecycleService, BranchLifecycleService>();
        services.AddSingleton<ITerminalRegistryService, TerminalRegistryService>();
        services.AddScoped<IInventoryService, InventoryService>();
        services.AddScoped<ILoyaltyService, LoyaltyService>();
        services.AddScoped<ISmsMarketingService, SmsMarketingService>();
        services.AddScoped<IHrService, HrService>();
        services.AddScoped<IReportService, ReportService>();
        services.AddScoped<IDailyReportService, DailyReportService>();
        services.AddScoped<IPublicService, PublicService>();
        services.AddScoped<IReviewService, ReviewService>();
        services.AddScoped<IPushDeviceService, PushDeviceService>();
        services.AddScoped<IBillingService, BillingService>();
        services.AddScoped<IBillingPaymentOrchestrator, BillingPaymentOrchestrator>();
        services.Configure<DeliveryPlatformsOptions>(configuration.GetSection(DeliveryPlatformsOptions.SectionName));
        services.PostConfigure<DeliveryPlatformsOptions>(opts =>
        {
            if (string.IsNullOrEmpty(opts.Snappfood.WebhookSecret))
                opts.Snappfood.WebhookSecret = configuration["Snappfood:WebhookSecret"] ?? "";
            if (string.IsNullOrEmpty(opts.Snappfood.ApiKey))
                opts.Snappfood.ApiKey = configuration["Snappfood:ApiKey"] ?? "";
            if (string.IsNullOrEmpty(opts.Snappfood.ApiBaseUrl))
                opts.Snappfood.ApiBaseUrl = configuration["Snappfood:ApiBaseUrl"] ?? "";
        });

        services.AddScoped<IWebhookSignatureService, WebhookSignatureService>();
        services.AddScoped<IOrderNormalizer, OrderNormalizer>();
        services.AddScoped<ICommissionCalculator, CommissionCalculator>();
        services.AddScoped<IDeliveryOrderProcessor, DeliveryOrderProcessor>();
        services.AddScoped<IDeliveryWebhookIngressService, DeliveryWebhookIngressService>();
        services.AddScoped<IDeliveryStatusSyncService, DeliveryStatusSyncService>();
        services.AddScoped<IDeliveryFinanceReportService, DeliveryFinanceReportService>();
        services.AddScoped<ProcessDeliveryOrderJob>();
        services.AddScoped<ISnappfoodWebhookService, SnappfoodWebhookService>();
        services.AddScoped<IReservationService, ReservationService>();
        services.AddScoped<IQueueService, QueueService>();
        services.AddScoped<IShiftService, ShiftService>();
        services.AddScoped<IExpenseService, ExpenseService>();
        services.AddScoped<IDemoSeedService, DemoSeedService>();
        services.AddScoped<ReceiptBuilder>();
        services.AddScoped<IPrinterService, NetworkPrinterService>();
        services.AddHttpClient(nameof(PosDeviceService));
        services.AddScoped<IPosDeviceService, PosDeviceService>();
        services.AddScoped<SubscriptionRenewalReminderJob>();

        services.AddControllers()
            .AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
                options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
            });
        services.AddEndpointsApiExplorer();
        services.AddSwaggerGen();
        services.AddSignalR();

        services.AddValidatorsFromAssemblyContaining<Program>();

        var jwtKey = configuration["Jwt:Key"] ?? "meezi-dev-secret-key-min-32-chars!!";
        var jwtIssuer = configuration["Jwt:Issuer"] ?? "meezi";
        var jwtAudience = configuration["Jwt:Audience"] ?? "meezi";

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = jwtIssuer,
                    ValidAudience = jwtAudience,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
                };
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        var accessToken = context.Request.Query["access_token"];
                        var path = context.HttpContext.Request.Path;
                        if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
                            context.Token = accessToken;
                        return Task.CompletedTask;
                    }
                };
            });

        services.AddAuthorization();

        var isTesting = configuration.GetValue<bool>("Testing:Enabled");
        var redisConnection = configuration.GetConnectionString("Redis") ?? "localhost:6379";
        if (isTesting)
        {
            services.AddSingleton<IConnectionMultiplexer>(_ =>
                ConnectionMultiplexer.Connect($"{redisConnection},abortConnect=false,connectTimeout=500"));
        }
        else
        {
            services.AddSingleton<IConnectionMultiplexer>(_ =>
                ConnectionMultiplexer.Connect(redisConnection));
        }

        var connectionString = configuration.GetConnectionString("DefaultConnection")
            ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

        services.AddHangfire(config =>
        {
            config
                .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
                .UseSimpleAssemblyNameTypeSerializer()
                .UseRecommendedSerializerSettings();

            if (isTesting)
                config.UseMemoryStorage();
            else
                config.UsePostgreSqlStorage(options => options.UseNpgsqlConnection(connectionString));
        });

        if (!isTesting)
            services.AddHangfireServer();

        services.AddCors(options =>
        {
            options.AddPolicy("MeeziCors", policy =>
            {
                var origins = configuration.GetSection("Cors:Origins").Get<string[]>()
                    ?? ["http://localhost:3000"];
                policy.WithOrigins(origins)
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });

        return services;
    }

    public static WebApplication ConfigureMeeziPipeline(this WebApplication app)
    {
        app.UseSerilogRequestLogging();

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();

        var uploadsPath = Path.Combine(app.Environment.ContentRootPath, "uploads");
        Directory.CreateDirectory(uploadsPath);
        app.UseStaticFiles(new StaticFileOptions
        {
            FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(uploadsPath),
            RequestPath = "/uploads"
        });

        app.UseCors("MeeziCors");
        app.UseRouting();
        app.UseMeeziSecurity();
        app.UseAuthentication();
        app.UseMiddleware<Middleware.TenantMiddleware>();
        app.UseMiddleware<Middleware.PlanLimitMiddleware>();
        app.UseAuthorization();

        app.MapControllers();
        app.MapHub<KdsHub>("/hubs/kds");
        app.MapHub<GuestOrderHub>("/hubs/guest-order");
        app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));

        if (!app.Configuration.GetValue<bool>("Testing:Enabled"))
        {
            app.UseHangfireDashboard("/hangfire");

            RecurringJob.AddOrUpdate<SubscriptionRenewalReminderJob>(
                "subscription-renewal-reminder",
                job => job.ExecuteAsync(),
                Cron.Daily(6));

            RecurringJob.AddOrUpdate<GenerateYesterdayReportsJob>(
                "daily-reports-yesterday",
                job => job.ExecuteAsync(),
                "5 0 * * *",
                new RecurringJobOptions { TimeZone = IranCalendar.TimeZone });

            RecurringJob.AddOrUpdate<BranchPermanentDeleteJob>(
                "branch-permanent-delete",
                job => job.ExecuteAsync(),
                Cron.Hourly);
        }

        return app;
    }
}
