using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using PlotLine.Data;
using PlotLine.Models;
using PlotLine.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.FileProviders;
using PlotLine.Hubs;

namespace PlotLine;

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Logging.ClearProviders();
        builder.Logging.AddConsole();
        builder.Logging.AddDebug();

        builder.Services.AddControllersWithViews(options =>
        {
            options.Filters.Add<ProjectAccessFilter>();
        });
        builder.Services.AddSignalR(options =>
        {
            options.MaximumReceiveMessageSize = 1024 * 1024;
        });
        builder.Services.AddHttpContextAccessor();
        builder.Services.AddMemoryCache();
        builder.Services.AddDistributedMemoryCache();
        builder.Services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromMinutes(10);
            options.Cookie.HttpOnly = true;
            options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
            options.Cookie.SameSite = SameSiteMode.Lax;
        });
        builder.Services.Configure<EmailSettings>(builder.Configuration.GetSection("EmailSettings"));
        builder.Services.Configure<StripeSettings>(builder.Configuration.GetSection("Stripe"));
        builder.Services.Configure<StorageSettings>(builder.Configuration.GetSection("Storage"));
        builder.Services.Configure<StoryIntelligenceOptions>(options =>
        {
            builder.Configuration.GetSection("OpenAI").Bind(options);
            builder.Configuration.GetSection("StoryIntelligence").Bind(options);
        });
        builder.Services.Configure<StoryIntelligencePricingOptions>(builder.Configuration.GetSection("StoryIntelligence:Pricing"));
        var dataProtectionKeyPath = GetDataProtectionKeyPath(
            builder.Environment.ContentRootPath,
            builder.Configuration["DataProtection:KeysPath"]);
        Directory.CreateDirectory(dataProtectionKeyPath);
        builder.Services.AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeyPath));
        builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options =>
            {
                options.Cookie.Name = "PlotLine.Auth";
                options.LoginPath = "/Account/Login";
                options.LogoutPath = "/Account/Logout";
                options.AccessDeniedPath = "/Account/AccessDenied";
                options.SlidingExpiration = true;
                options.ExpireTimeSpan = TimeSpan.FromDays(14);
                options.Cookie.HttpOnly = true;
                options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                options.Cookie.SameSite = SameSiteMode.Lax;
                options.Events = new CookieAuthenticationEvents
                {
                    OnValidatePrincipal = async context =>
                    {
                        var userIdValue = context.Principal?.FindFirstValue(ClaimTypes.NameIdentifier);
                        var securityStampValue = context.Principal?.FindFirstValue("PlotLine.SecurityStamp");
                        if (!int.TryParse(userIdValue, out var userId) || !Guid.TryParse(securityStampValue, out var securityStamp))
                        {
                            context.RejectPrincipal();
                            await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                            return;
                        }

                        using var scope = context.HttpContext.RequestServices.CreateScope();
                        var users = scope.ServiceProvider.GetRequiredService<IUserRepository>();
                        var user = await users.GetByIdAsync(userId);
                        var isLocked = user?.IsLocked == true && (!user.LockoutEndUtc.HasValue || user.LockoutEndUtc > DateTime.UtcNow);
                        if (user is null || user.SecurityStamp != securityStamp || isLocked)
                        {
                            context.RejectPrincipal();
                            await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                        }
                    }
                };
            });
        builder.Services.AddAuthorization(options =>
        {
            options.AddPolicy("AdminOnly", policy =>
            {
                policy.RequireAuthenticatedUser();
                policy.RequireAssertion(context =>
                {
                    var email = context.User.FindFirstValue(ClaimTypes.Email);
                    var allowedEmails = builder.Configuration.GetSection("Admin:AllowedEmails").Get<string[]>() ?? [];
                    return !string.IsNullOrWhiteSpace(email)
                        && allowedEmails.Any(allowed => string.Equals(allowed, email, StringComparison.OrdinalIgnoreCase));
                });
            });
        });
        builder.Services.AddSingleton<ISqlConnectionFactory, SqlConnectionFactory>();
        builder.Services.AddScoped<IUserRepository, UserRepository>();
        builder.Services.AddScoped<IAuthenticationRepository, AuthenticationRepository>();
        builder.Services.AddScoped<IEmailQueueRepository, EmailQueueRepository>();
        builder.Services.AddScoped<IUserFileRepository, UserFileRepository>();
        builder.Services.AddScoped<IFeatureRequestRepository, FeatureRequestRepository>();
        builder.Services.AddScoped<IProjectRepository, ProjectRepository>();
        builder.Services.AddScoped<IBookRepository, BookRepository>();
        builder.Services.AddScoped<IManuscriptDocumentRepository, ManuscriptDocumentRepository>();
        builder.Services.AddScoped<IChapterRepository, ChapterRepository>();
        builder.Services.AddScoped<ISceneRepository, SceneRepository>();
        builder.Services.AddScoped<ISceneFloorPlanOccupancyRepository, SceneFloorPlanOccupancyRepository>();
        builder.Services.AddScoped<ILookupRepository, LookupRepository>();
        builder.Services.AddScoped<ITimelineRepository, TimelineRepository>();
        builder.Services.AddScoped<ITimelinePresetRepository, TimelinePresetRepository>();
        builder.Services.AddScoped<ITimelineSettingsRepository, TimelineSettingsRepository>();
        builder.Services.AddScoped<ISceneMetricTypeRepository, SceneMetricTypeRepository>();
        builder.Services.AddScoped<IPlotRepository, PlotRepository>();
        builder.Services.AddScoped<IAssetRepository, AssetRepository>();
        builder.Services.AddScoped<ICharacterRepository, CharacterRepository>();
        builder.Services.AddScoped<ILocationRepository, LocationRepository>();
        builder.Services.AddScoped<IFloorPlanRepository, FloorPlanRepository>();
        builder.Services.AddScoped<IWarningRepository, WarningRepository>();
        builder.Services.AddScoped<IContinuityWarningAcknowledgementRepository, ContinuityWarningAcknowledgementRepository>();
        builder.Services.AddScoped<ISceneDependencyRepository, SceneDependencyRepository>();
        builder.Services.AddScoped<IAnalyticsRepository, AnalyticsRepository>();
        builder.Services.AddScoped<IStoryBibleRepository, StoryBibleRepository>();
        builder.Services.AddScoped<IWriterWorkspaceRepository, WriterWorkspaceRepository>();
        builder.Services.AddScoped<IWritingScheduleRepository, WritingScheduleRepository>();
        builder.Services.AddScoped<IExportRepository, ExportRepository>();
        builder.Services.AddScoped<IProjectBackupRepository, ProjectBackupRepository>();
        builder.Services.AddScoped<IProjectRestorePointRepository, ProjectRestorePointRepository>();
        builder.Services.AddScoped<IDynamicsRepository, DynamicsRepository>();
        builder.Services.AddScoped<IScenarioRepository, ScenarioRepository>();
        builder.Services.AddScoped<IArchiveRepository, ArchiveRepository>();
        builder.Services.AddScoped<IAcceptanceRepository, AcceptanceRepository>();
        builder.Services.AddScoped<IImportRepository, ImportRepository>();
        builder.Services.AddScoped<IWordCompanionRepository, WordCompanionRepository>();
        builder.Services.AddScoped<IOnboardingRepository, OnboardingRepository>();
        builder.Services.AddScoped<IOnboardingBuildRepository, OnboardingBuildRepository>();
        builder.Services.AddScoped<IStoryIntelligenceRepository, StoryIntelligenceRepository>();
        builder.Services.AddScoped<IStoryIntelligenceResultRepository, StoryIntelligenceResultRepository>();
        builder.Services.AddScoped<IStoryIntelligenceSourceRepository, StoryIntelligenceSourceRepository>();
        builder.Services.AddScoped<IStoryIntelligencePipelineRepository, StoryIntelligencePipelineRepository>();
        builder.Services.AddScoped<IStoryIntelligenceIllustrationMatchingRepository, StoryIntelligenceIllustrationMatchingRepository>();
        builder.Services.AddScoped<IStoryMemoryRepository, StoryMemoryRepository>();
        builder.Services.AddScoped<IIllustrationLibraryRepository, IllustrationLibraryRepository>();
        builder.Services.AddScoped<IProjectAccessRepository, ProjectAccessRepository>();
        builder.Services.AddScoped<IProjectCollaborationRepository, ProjectCollaborationRepository>();
        builder.Services.AddScoped<IProjectActivityRepository, ProjectActivityRepository>();
        builder.Services.AddScoped<ISubscriptionRepository, SubscriptionRepository>();
        builder.Services.AddScoped<IProjectService, ProjectService>();
        builder.Services.AddScoped<IHardDeleteFileCleanupService, HardDeleteFileCleanupService>();
        builder.Services.AddScoped<IProjectCollaborationService, ProjectCollaborationService>();
        builder.Services.AddScoped<IProjectActivityService, ProjectActivityService>();
        builder.Services.AddScoped<IBookCoverService, BookCoverService>();
        builder.Services.AddScoped<IVisualIdentityImageService, VisualIdentityImageService>();
        builder.Services.AddScoped<IFloorPlanBackgroundImageService, FloorPlanBackgroundImageService>();
        builder.Services.AddScoped<IBookService, BookService>();
        builder.Services.AddScoped<IChapterService, ChapterService>();
        builder.Services.AddScoped<ISceneService, SceneService>();
        builder.Services.AddScoped<IStoryStateService, StoryStateService>();
        builder.Services.AddScoped<ITimelineService, TimelineService>();
        builder.Services.AddScoped<ISceneMetricTypeService, SceneMetricTypeService>();
        builder.Services.AddScoped<ISceneMetricValueService, SceneMetricValueService>();
        builder.Services.AddScoped<IPlotService, PlotService>();
        builder.Services.AddScoped<IAssetService, AssetService>();
        builder.Services.AddScoped<ICharacterService, CharacterService>();
        builder.Services.AddScoped<ILocationService, LocationService>();
        builder.Services.AddScoped<IFloorPlanService, FloorPlanService>();
        builder.Services.AddScoped<IContinuityValidationService, ContinuityValidationService>();
        builder.Services.AddScoped<IAnalyticsService, AnalyticsService>();
        builder.Services.AddScoped<IStoryBibleService, StoryBibleService>();
        builder.Services.AddScoped<IRelationshipMapService, RelationshipMapService>();
        builder.Services.AddScoped<IWriterWorkspaceService, WriterWorkspaceService>();
        builder.Services.AddScoped<IWritingScheduleService, WritingScheduleService>();
        builder.Services.AddScoped<IStorageService, StorageService>();
        builder.Services.AddSingleton<IUploadStorageService, UploadStorageService>();
        builder.Services.AddScoped<IExportService, ExportService>();
        builder.Services.AddScoped<IProjectExportService, ProjectExportService>();
        builder.Services.AddScoped<IProjectRestorePointService, ProjectRestorePointService>();
        builder.Services.AddScoped<IProjectRestoreService, ProjectRestoreService>();
        builder.Services.AddScoped<IDynamicsService, DynamicsService>();
        builder.Services.AddScoped<IScenarioService, ScenarioService>();
        builder.Services.AddScoped<IArchiveService, ArchiveService>();
        builder.Services.AddScoped<IAcceptanceService, AcceptanceService>();
        builder.Services.AddScoped<IImportService, ImportService>();
        builder.Services.AddScoped<IWordCompanionService, WordCompanionService>();
        builder.Services.AddSingleton<IWordCompanionPresenceService, WordCompanionPresenceService>();
        builder.Services.AddSingleton<IManuscriptScanPreviewStore, ManuscriptScanPreviewStore>();
        builder.Services.AddSingleton<IOnboardingStoryIntelligenceBatchStore, OnboardingStoryIntelligenceBatchStore>();
        builder.Services.AddHostedService<WordCompanionPresenceMonitor>();
        builder.Services.AddScoped<IOnboardingService, OnboardingService>();
        builder.Services.AddScoped<IOnboardingStoryIntelligenceService, OnboardingStoryIntelligenceService>();
        builder.Services.AddScoped<IStoryIntelligenceService, StoryIntelligenceService>();
        builder.Services.AddScoped<IStoryIntelligenceProvider, StubStoryIntelligenceProvider>();
        builder.Services.AddSingleton<IStoryPromptRepository, StoryPromptRepository>();
        builder.Services.AddSingleton<IStoryPromptBuilder, StoryPromptBuilder>();
        builder.Services.AddSingleton<IStoryPromptVersionService, StoryPromptVersionService>();
        builder.Services.AddHttpClient<IStoryIntelligenceClient, StoryIntelligenceClient>();
        builder.Services.AddScoped<IStorySceneValidator, StorySceneValidator>();
        builder.Services.AddScoped<IChapterStructureValidator, ChapterStructureValidator>();
        builder.Services.AddScoped<IStoryIntelligenceDiagnosticsService, StoryIntelligenceDiagnosticsService>();
        builder.Services.AddScoped<IStoryIntelligenceDryRunService, StoryIntelligenceDryRunService>();
        builder.Services.AddScoped<IChapterStructureDryRunService, ChapterStructureDryRunService>();
        builder.Services.AddScoped<IChapterStoryIntelligenceDryRunService, ChapterStoryIntelligenceDryRunService>();
        builder.Services.AddScoped<IStoryIntelligenceResultPersistenceService, StoryIntelligenceResultPersistenceService>();
        builder.Services.AddScoped<IStoryIntelligenceExistingChapterQueueService, StoryIntelligenceExistingChapterQueueService>();
        builder.Services.AddScoped<IStoryIntelligenceFullChapterTestService, StoryIntelligenceFullChapterTestService>();
        builder.Services.AddScoped<IStoryIntelligenceImportCommitService, StoryIntelligenceImportCommitService>();
        builder.Services.AddScoped<IStoryIntelligenceCharacterImportService, StoryIntelligenceCharacterImportService>();
        builder.Services.AddScoped<IStoryIntelligenceLocationImportService, StoryIntelligenceLocationImportService>();
        builder.Services.AddScoped<IStoryIntelligenceAssetImportService, StoryIntelligenceAssetImportService>();
        builder.Services.AddScoped<IStoryIntelligenceRelationshipImportService, StoryIntelligenceRelationshipImportService>();
        builder.Services.AddScoped<IStoryIntelligenceKnowledgeImportService, StoryIntelligenceKnowledgeImportService>();
        builder.Services.AddScoped<IStoryIntelligenceVisualisationSnapshotService, StoryIntelligenceVisualisationSnapshotService>();
        builder.Services.AddScoped<IStoryMemoryService, StoryMemoryService>();
        builder.Services.AddScoped<IStoryIntelligencePipelineStateService, StoryIntelligencePipelineStateService>();
        builder.Services.AddScoped<IStoryIntelligenceProgressNotifier, StoryIntelligenceProgressNotifier>();
        builder.Services.AddScoped<ISceneImportResolver, SceneImportResolver>();
        builder.Services.AddScoped<IPersistedStoryIntelligenceRunner, PersistedStoryIntelligenceRunner>();
        builder.Services.AddScoped<IIllustrationPromptBuilder, IllustrationPromptBuilder>();
        builder.Services.AddScoped<IIllustrationLibraryStorageService, IllustrationLibraryStorageService>();
        builder.Services.AddHttpClient<IIllustrationImageProvider, OpenAIIllustrationImageProvider>();
        builder.Services.AddScoped<IIllustrationGenerationRunner, IllustrationGenerationRunner>();
        builder.Services.AddScoped<IIllustrationLibraryService, IllustrationLibraryService>();
        builder.Services.AddScoped<IStoryIntelligenceIllustrationMatchingService, StoryIntelligenceIllustrationMatchingService>();
        builder.Services.AddScoped<IProjectAccessService, ProjectAccessService>();
        builder.Services.AddScoped<ISubscriptionService, SubscriptionService>();
        builder.Services.AddScoped<IStripeBillingService, StripeBillingService>();
        builder.Services.AddSingleton<IHelpService, HelpService>();
        builder.Services.AddSingleton<IHelpMarkdownRenderer, HelpMarkdownRenderer>();
        builder.Services.AddScoped<IAuthService, AuthService>();
        builder.Services.AddScoped<IAccountDeletionService, AccountDeletionService>();
        builder.Services.AddScoped<IPasswordService, PasswordService>();
        builder.Services.AddScoped<ITwoFactorService, TwoFactorService>();
        builder.Services.AddScoped<IEmailService, EmailService>();
        builder.Services.AddScoped<IQueuedEmailSender, QueuedEmailSender>();
        builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
        builder.Services.AddScoped<IFeatureRequestService, FeatureRequestService>();
        builder.Services.AddHostedService<EmailQueueWorker>();
        if (builder.Configuration.GetValue("StoryIntelligence:WorkersEnabled", true))
        {
            builder.Services.AddHostedService<StoryIntelligenceWorker>();
            builder.Services.AddHostedService<PersistedStoryIntelligenceWorker>();
        }
        if (builder.Configuration.GetValue("Illustrations:GenerationWorkerEnabled", true))
        {
            builder.Services.AddHostedService<IllustrationGenerationWorker>();
        }

        var app = builder.Build();

        if (!app.Environment.IsDevelopment())
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        var uploadStorage = app.Services.GetRequiredService<IUploadStorageService>();
        app.UseStaticFiles(new StaticFileOptions
        {
            FileProvider = new PhysicalFileProvider(uploadStorage.UploadsRootPath),
            RequestPath = "/uploads"
        });
        app.UseRouting();

        app.UseSession();
        app.UseAuthentication();
        app.UseAuthorization();

        app.MapStaticAssets();
        app.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}")
            .WithStaticAssets();
        app.MapHub<WordCompanionFollowHub>("/hubs/word-companion-follow");
        app.MapHub<StoryIntelligenceHub>("/hubs/story-intelligence");

        app.Run();
    }

    private static string GetDataProtectionKeyPath(string contentRootPath, string? configuredPath)
    {
        if (!string.IsNullOrWhiteSpace(configuredPath))
        {
            return Path.GetFullPath(configuredPath);
        }

        var contentRoot = new DirectoryInfo(contentRootPath);
        if (string.Equals(contentRoot.Name, "current", StringComparison.OrdinalIgnoreCase)
            && contentRoot.Parent is not null)
        {
            return Path.Combine(contentRoot.Parent.FullName, "App_Data", "DataProtectionKeys");
        }

        return Path.Combine(contentRoot.FullName, "App_Data", "DataProtectionKeys");
    }
}
