using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MudBlazor.Services;
using Serilog;
using timetracker.Server.Components;
using timetracker.Data;
using timetracker.Shared;

var builder = WebApplication.CreateBuilder(args);
const string SuperAdminUsername = "marc";
const string SuperAdminPassword = "123456";

var logDirectory = Environment.GetEnvironmentVariable("TIMETRACKER_LOG_DIR")
    ?? Path.Combine(builder.Environment.ContentRootPath, "logs");
Directory.CreateDirectory(logDirectory);

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.File(
        Path.Combine(logDirectory, "timetracker-.log"),
        rollingInterval: RollingInterval.Day,
        retainedFileCountLimit: 14,
        shared: true,
        flushToDiskInterval: TimeSpan.FromSeconds(1))
    .CreateLogger();

builder.Host.UseSerilog();

// Add Authentication
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/login";
        options.LogoutPath = "/auth/logout";
        options.ExpireTimeSpan = TimeSpan.FromDays(30);
        options.SlidingExpiration = true;
        options.Cookie.HttpOnly = true;
        options.Cookie.SameSite = SameSiteMode.Strict;
        options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
        // Cookie-Konfiguration für APIs & WASM
        options.Events.OnRedirectToLogin = context =>
        {
            if (context.Request.Path.StartsWithSegments("/api"))
            {
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            }
            else
            {
                context.Response.Redirect(context.RedirectUri);
            }
            return Task.CompletedTask;
        };
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireClaim(ClaimTypes.Name, "marc"));
});

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddHttpContextAccessor();

builder.Services.AddTransient<ITenantProvider, TenantProvider>();

// Add SignalR
builder.Services.AddSignalR();
builder.Services.AddSingleton<UserNotificationService>();
builder.Services.AddSingleton<IUserNotificationService>(sp => sp.GetRequiredService<UserNotificationService>());

// Register DB-backed services as the interfaces
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<ITimetrackerService, TimetrackerService>();
builder.Services.AddScoped<IHolidayService, HolidayService>();

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents();

builder.Services.AddMudServices();
builder.Services.AddHttpClient<HolidayService>();
builder.Services.AddScoped(sp =>
{
    try
    {
        var navigationManager = sp.GetService<Microsoft.AspNetCore.Components.NavigationManager>();
        if (navigationManager != null && !string.IsNullOrEmpty(navigationManager.BaseUri))
        {
            return new System.Net.Http.HttpClient { BaseAddress = new Uri(navigationManager.BaseUri) };
        }
    }
    catch (InvalidOperationException)
    {
        // NavigationManager is registered but not initialized in this request scope
    }
    
    var httpContextAccessor = sp.GetService<Microsoft.AspNetCore.Http.IHttpContextAccessor>();
    var request = httpContextAccessor?.HttpContext?.Request;
    if (request != null)
    {
        var baseUri = $"{request.Scheme}://{request.Host}{request.PathBase}/";
        return new System.Net.Http.HttpClient { BaseAddress = new Uri(baseUri) };
    }
    
    return new System.Net.Http.HttpClient();
});

builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    options.AddPolicy("auth-limit", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() 
                ?? httpContext.Request.Headers["X-Forwarded-For"].ToString() 
                ?? "unknown",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                Window = TimeSpan.FromMinutes(1),
                PermitLimit = 5,
                QueueLimit = 0
            }));
});

var dbProvider = builder.Configuration["DB_PROVIDER"] ?? "SQLite";

if (dbProvider.Equals("PostgreSQL", StringComparison.OrdinalIgnoreCase))
{
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") 
        ?? builder.Configuration["ConnectionStrings:DefaultConnection"];
    builder.Services.AddDbContextFactory<TimetrackerDbContext>(options =>
        options.UseNpgsql(connectionString)
               .ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)));
}
else
{
    var dbPath = Environment.GetEnvironmentVariable("TIMETRACKER_DB_PATH")
        ?? Path.Combine(builder.Environment.ContentRootPath, "timetracker.db");
    builder.Services.AddDbContextFactory<TimetrackerDbContext>(options =>
        options.UseSqlite($"Data Source={dbPath}")
               .ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)));
}

var app = builder.Build();

app.Logger.LogInformation("File logging active. Writing logs to {LogDirectory}", logDirectory);

// Migrate or Ensure Database Created
using (var scope = app.Services.CreateScope())
{
    var factory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<TimetrackerDbContext>>();
    await using var db = await factory.CreateDbContextAsync();
    
    if (dbProvider.Equals("PostgreSQL", StringComparison.OrdinalIgnoreCase))
    {
        try
        {
            var conn = db.Database.GetDbConnection();
            if (conn.State != System.Data.ConnectionState.Open)
            {
                await conn.OpenAsync();
            }

            // Check if AppSettings table already exists
            using (var cmdCheck = conn.CreateCommand())
            {
                cmdCheck.CommandText = "SELECT EXISTS (SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'AppSettings');";
                var appSettingsExists = (bool)(await cmdCheck.ExecuteScalarAsync() ?? false);

                if (appSettingsExists)
                {
                    // Ensure __EFMigrationsHistory table exists
                    using (var cmdCreateHistory = conn.CreateCommand())
                    {
                        cmdCreateHistory.CommandText = @"
                            CREATE TABLE IF NOT EXISTS ""__EFMigrationsHistory"" (
                                ""MigrationId"" character varying(150) NOT NULL,
                                ""ProductVersion"" character varying(32) NOT NULL,
                                CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY (""MigrationId"")
                            );";
                        await cmdCreateHistory.ExecuteNonQueryAsync();
                    }

                    // Check if the initial migration is already recorded
                    using (var cmdCheckHistory = conn.CreateCommand())
                    {
                        cmdCheckHistory.CommandText = @"SELECT COUNT(*) FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260520133634_Initial';";
                        var historyCount = Convert.ToInt32(await cmdCheckHistory.ExecuteScalarAsync() ?? 0);

                        if (historyCount == 0)
                        {
                            // Seed the history table with the migrations that were already created by EnsureCreated in older versions
                            using (var cmdInsertHistory = conn.CreateCommand())
                            {
                                cmdInsertHistory.CommandText = @"
                                    INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") VALUES
                                    ('20260520133634_Initial', '10.0.9'),
                                    ('20260520200000_AddPublicHolidays', '10.0.9'),
                                    ('20260522081459_AddMultiUser', '10.0.9'),
                                    ('20260607213215_AddFlexTimeAndHolidayState', '10.0.9');";
                                await cmdInsertHistory.ExecuteNonQueryAsync();
                            }
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Fehler beim Vorbereiten der PostgreSQL Migrationshistorie: {ex.Message}");
        }
    }

    await db.Database.MigrateAsync();

    if (dbProvider.Equals("PostgreSQL", StringComparison.OrdinalIgnoreCase))
    {
        try
        {
            var conn = db.Database.GetDbConnection();
            if (conn.State != System.Data.ConnectionState.Open)
            {
                await conn.OpenAsync();
            }

            foreach (var tableName in new[] { "Users", "Tenants", "AppSettings", "WorkDays", "BreakEntries", "VacationDays", "PublicHolidays" })
            {
                await EnsurePostgresIntegerPrimaryKeyDefaultAsync(conn, tableName);
            }

            // Check if column IsTenantAdmin in Users table is of type integer
            using (var cmdCheckType = conn.CreateCommand())
            {
                cmdCheckType.CommandText = @"
                    SELECT data_type 
                    FROM information_schema.columns 
                    WHERE table_name = 'Users' AND column_name = 'IsTenantAdmin';";
                var dataType = await cmdCheckType.ExecuteScalarAsync() as string;
                if (dataType != null && dataType.Equals("integer", StringComparison.OrdinalIgnoreCase))
                {
                    // Drop legacy integer default before changing the column type.
                    using (var cmdAlterType = conn.CreateCommand())
                    {
                        cmdAlterType.CommandText = @"
                            ALTER TABLE ""Users"" 
                            ALTER COLUMN ""IsTenantAdmin"" DROP DEFAULT,
                            ALTER COLUMN ""IsTenantAdmin"" TYPE boolean 
                            USING (""IsTenantAdmin"" <> 0),
                            ALTER COLUMN ""IsTenantAdmin"" SET DEFAULT FALSE;";
                        await cmdAlterType.ExecuteNonQueryAsync();
                    }
                }
            }

            // Check if column IsApproved in Tenants table is of type integer
            using (var cmdCheckApproved = conn.CreateCommand())
            {
                cmdCheckApproved.CommandText = @"
                    SELECT data_type 
                    FROM information_schema.columns 
                    WHERE table_name = 'Tenants' AND column_name = 'IsApproved';";
                var dataType = await cmdCheckApproved.ExecuteScalarAsync() as string;
                if (dataType != null && dataType.Equals("integer", StringComparison.OrdinalIgnoreCase))
                {
                    // Drop legacy integer default before changing the column type.
                    using (var cmdAlterApproved = conn.CreateCommand())
                    {
                        cmdAlterApproved.CommandText = @"
                            ALTER TABLE ""Tenants"" 
                            ALTER COLUMN ""IsApproved"" DROP DEFAULT,
                            ALTER COLUMN ""IsApproved"" TYPE boolean 
                            USING (""IsApproved"" <> 0),
                            ALTER COLUMN ""IsApproved"" SET DEFAULT FALSE;";
                        await cmdAlterApproved.ExecuteNonQueryAsync();
                    }
                }
            }

            // Check if column CreatedAt in Tenants table is of type text or character varying
            using (var cmdCheckCreatedAt = conn.CreateCommand())
            {
                cmdCheckCreatedAt.CommandText = @"
                    SELECT data_type 
                    FROM information_schema.columns 
                    WHERE table_name = 'Tenants' AND column_name = 'CreatedAt';";
                var dataType = await cmdCheckCreatedAt.ExecuteScalarAsync() as string;
                if (dataType != null && (dataType.Equals("text", StringComparison.OrdinalIgnoreCase) || dataType.Equals("character varying", StringComparison.OrdinalIgnoreCase)))
                {
                    // Alter the column type to timestamp without time zone
                    using (var cmdAlterCreatedAt = conn.CreateCommand())
                    {
                        cmdAlterCreatedAt.CommandText = @"
                            ALTER TABLE ""Tenants"" 
                            ALTER COLUMN ""CreatedAt"" TYPE timestamp without time zone 
                            USING (""CreatedAt""::timestamp without time zone);";
                        await cmdAlterCreatedAt.ExecuteNonQueryAsync();
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Fehler beim Konvertieren der PostgreSQL-Spaltentypen (IsTenantAdmin/IsApproved/CreatedAt): {ex.Message}");
        }
    }

    var superAdmin = await db.Users
        .IgnoreQueryFilters()
        .FirstOrDefaultAsync(u => u.TenantId == null && u.Username == SuperAdminUsername);

    if (superAdmin == null)
    {
        var (passwordHash, passwordSalt) = HashPassword(SuperAdminPassword);
        db.Users.Add(new User
        {
            Username = SuperAdminUsername,
            PasswordHash = passwordHash,
            PasswordSalt = passwordSalt,
            TenantId = null,
            IsTenantAdmin = true
        });
        await db.SaveChangesAsync();
    }
    else if (IsLegacySeedPassword(superAdmin.PasswordHash, superAdmin.PasswordSalt, SuperAdminPassword))
    {
        var (passwordHash, passwordSalt) = HashPassword(SuperAdminPassword);
        superAdmin.PasswordHash = passwordHash;
        superAdmin.PasswordSalt = passwordSalt;
        superAdmin.IsTenantAdmin = true;
        await db.SaveChangesAsync();
    }
}

var forwardedHeadersOptions = new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
};
forwardedHeadersOptions.KnownProxies.Clear();
forwardedHeadersOptions.KnownIPNetworks.Clear();
app.UseForwardedHeaders(forwardedHeadersOptions);

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
if (app.Configuration.GetValue("EnableHttpsRedirect", !app.Environment.IsDevelopment()))
{
    app.UseHttpsRedirection();
}

app.UseStaticFiles();
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();

app.MapStaticAssets();

// Map Hub
app.MapHub<NotificationHub>("/hubs/notifications");

// Map Blazor WASM
app.MapRazorComponents<App>()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(timetracker.Client._Imports).Assembly);

// ── Auth-API-Endpoints ────────────────────────────────────────────────────────
app.MapGet("/api/auth/me", (HttpContext ctx) =>
{
    if (ctx.User.Identity?.IsAuthenticated == true)
    {
        var idClaim = ctx.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        var name = ctx.User.Identity.Name ?? "";
        var isTenantAdmin = ctx.User.FindFirst("IsTenantAdmin")?.Value == "true";
        if (int.TryParse(idClaim, out var id))
        {
            return Results.Ok(new UserInfo { Id = id, Username = name, IsTenantAdmin = isTenantAdmin });
        }
    }
    return Results.Unauthorized();
});

app.MapPost("/api/auth/login", async (HttpContext ctx, [FromBody] LoginRequest req, IAuthService authService, ITenantProvider tenantProvider) =>
{
    var tenant = await tenantProvider.GetCurrentTenantAsync();
    if (tenant != null && !tenant.IsApproved)
    {
        return Results.BadRequest("Diese Firma ist noch nicht freigeschaltet.");
    }

    var user = await authService.LoginAsync(req.Username, req.Password);
    if (user == null)
        return Results.BadRequest("Benutzername oder Passwort falsch.");

    if (tenant != null)
    {
        if (user.TenantId != tenant.Id)
        {
            return Results.BadRequest("Benutzer gehört nicht zu dieser Firma.");
        }
    }
    else
    {
        if (user.Username != "marc" && user.TenantId != null)
        {
            return Results.BadRequest("Benutzer gehört zu einer Firma. Bitte melden Sie sich über Ihre Firmen-Subdomain an.");
        }
    }

    var claims = new List<Claim> {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.Username),
        new Claim("IsTenantAdmin", user.IsTenantAdmin ? "true" : "false")
    };
    if (user.TenantId.HasValue)
    {
        claims.Add(new Claim("TenantId", user.TenantId.Value.ToString()));
    }

    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
        new ClaimsPrincipal(identity),
        new AuthenticationProperties { IsPersistent = true });

    return Results.Ok(new UserInfo { Id = user.Id, Username = user.Username, IsTenantAdmin = user.IsTenantAdmin });
}).RequireRateLimiting("auth-limit");

app.MapPost("/api/auth/register", async (HttpContext ctx, [FromBody] RegisterRequest req, IAuthService authService, ITenantProvider tenantProvider) =>
{
    if (!string.IsNullOrEmpty(req.Honeypot))
    {
        return Results.BadRequest("Registrierung fehlgeschlagen.");
    }

    var tenant = await tenantProvider.GetCurrentTenantAsync();
    if (tenant != null && !tenant.IsApproved)
    {
        return Results.BadRequest("Registrierung blockiert: Firma noch nicht freigeschaltet.");
    }

    var (user, error) = await authService.RegisterAsync(req.Username, req.Password);
    if (user == null)
        return Results.BadRequest(error ?? "Registrierung fehlgeschlagen.");

    var claims = new List<Claim> {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.Username),
        new Claim("IsTenantAdmin", user.IsTenantAdmin ? "true" : "false")
    };
    if (user.TenantId.HasValue)
    {
        claims.Add(new Claim("TenantId", user.TenantId.Value.ToString()));
    }

    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
        new ClaimsPrincipal(identity),
        new AuthenticationProperties { IsPersistent = true });

    return Results.Ok(new UserInfo { Id = user.Id, Username = user.Username, IsTenantAdmin = user.IsTenantAdmin });
}).RequireRateLimiting("auth-limit");

app.MapGet("/auth/logout", async (HttpContext ctx) =>
{
    await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return Results.Redirect("/login");
});

// ── Admin-API-Endpoints (Protected) ───────────────────────────────────────────
var usersApi = app.MapGroup("/api/users").RequireAuthorization("AdminOnly");

usersApi.MapGet("/", async (IAuthService authService) =>
{
    var users = await authService.GetAllUsersAsync();
    return Results.Ok(users);
});

usersApi.MapDelete("/{userId:int}", async (int userId, IAuthService authService) =>
{
    await authService.DeleteUserAsync(userId);
    return Results.NoContent();
});

usersApi.MapPut("/{userId:int}/rename", async (int userId, [FromBody] RenameRequest req, IAuthService authService) =>
{
    var error = await authService.RenameUserAsync(userId, req.Username);
    if (error != null)
    {
        return Results.BadRequest(error);
    }
    return Results.Ok();
});

usersApi.MapPut("/{userId:int}/change-password", async (int userId, [FromBody] ChangePasswordRequest req, IAuthService authService) =>
{
    var error = await authService.ChangePasswordAsync(userId, req.CurrentPassword, req.NewPassword);
    if (error != null)
    {
        return Results.BadRequest(error);
    }
    return Results.Ok();
});

usersApi.MapPut("/{userId:int}/assign-tenant", async (int userId, [FromBody] AssignTenantRequest req, IAuthService authService) =>
{
    var error = await authService.AssignTenantAsync(userId, req.TenantId);
    if (error != null)
    {
        return Results.BadRequest(error);
    }
    return Results.Ok();
});

usersApi.MapPut("/{userId:int}/reset-password", async (int userId, [FromBody] ResetPasswordRequest req, IAuthService authService) =>
{
    var error = await authService.ResetPasswordAsync(userId, req.NewPassword);
    if (error != null)
    {
        return Results.BadRequest(error);
    }
    return Results.Ok();
});

// ── Timetracker-API-Endpoints (Protected) ─────────────────────────────────────
var trackerApi = app.MapGroup("/api/tracker").RequireAuthorization();

trackerApi.MapPost("/tenant/settings", async (ClaimsPrincipal claimsPrincipal, [FromBody] TenantSettingsDto dto, ITenantProvider tenantProvider, TimetrackerDbContext db) =>
{
    var isAdmin = claimsPrincipal.FindFirst("IsTenantAdmin")?.Value == "true";
    if (!isAdmin) return Results.Forbid();

    var tenant = await tenantProvider.GetCurrentTenantAsync();
    if (tenant == null) return Results.BadRequest("Keine Firma aktiv.");

    var dbTenant = await db.Tenants.FindAsync(tenant.Id);
    if (dbTenant == null) return Results.NotFound("Firma nicht gefunden.");

    if (!string.IsNullOrWhiteSpace(dto.Name))
    {
        dbTenant.Name = dto.Name.Trim();
    }
    if (!string.IsNullOrWhiteSpace(dto.PrimaryColor))
    {
        dbTenant.PrimaryColor = dto.PrimaryColor.Trim();
    }
    if (!string.IsNullOrWhiteSpace(dto.SecondaryColor))
    {
        dbTenant.SecondaryColor = dto.SecondaryColor.Trim();
    }

    await db.SaveChangesAsync();
    return Results.Ok();
});

trackerApi.MapGet("/week", async (ClaimsPrincipal claimsPrincipal, [FromQuery] string monday, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    if (DateOnly.TryParse(monday, out var date))
    {
        var days = await trackerService.GetWeekAsync(userId, date);
        return Results.Ok(days);
    }
    return Results.BadRequest("Ungültiges Datum.");
});

trackerApi.MapPost("/workday", async (ClaimsPrincipal claimsPrincipal, [FromBody] WorkDay workDay, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    workDay.UserId = userId; // Enforce owner
    await trackerService.UpsertWorkDayAsync(workDay);
    return Results.Ok();
});

trackerApi.MapGet("/settings", async (ClaimsPrincipal claimsPrincipal, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    var settings = await trackerService.GetSettingsAsync(userId);
    return Results.Ok(settings);
});

trackerApi.MapPost("/settings", async (ClaimsPrincipal claimsPrincipal, [FromBody] AppSettings settings, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    settings.UserId = userId; // Enforce owner
    await trackerService.SaveSettingsAsync(settings);
    return Results.Ok();
});

trackerApi.MapGet("/vacation/{year:int}", async (ClaimsPrincipal claimsPrincipal, int year, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    var days = await trackerService.GetVacationDaysAsync(userId, year);
    return Results.Ok(days);
});

trackerApi.MapPost("/vacation", async (ClaimsPrincipal claimsPrincipal, [FromBody] VacationDay vacationDay, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    vacationDay.UserId = userId; // Enforce owner
    await trackerService.AddVacationDayAsync(vacationDay);
    return Results.Ok();
});

trackerApi.MapDelete("/vacation/{id:int}", async (ClaimsPrincipal claimsPrincipal, int id, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    await trackerService.RemoveVacationDayAsync(userId, id);
    return Results.NoContent();
});

trackerApi.MapPost("/overtime", async (ClaimsPrincipal claimsPrincipal, [FromBody] AppSettings settings, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    settings.UserId = userId; // Enforce owner
    var ts = await trackerService.GetTotalOvertimeAsync(userId, settings);
    return Results.Ok(ts.TotalHours);
});

trackerApi.MapGet("/month", async (ClaimsPrincipal claimsPrincipal, [FromQuery] int year, [FromQuery] int month, ITimetrackerService trackerService) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    var days = await trackerService.GetMonthAsync(userId, year, month);
    return Results.Ok(days);
});

trackerApi.MapGet("/export/week", async (ClaimsPrincipal claimsPrincipal, [FromQuery] string monday, ITimetrackerService trackerService, TimetrackerDbContext db) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    if (DateOnly.TryParse(monday, out var date))
    {
        var mondayDate = date;
        int diff = ((int)mondayDate.DayOfWeek - (int)DayOfWeek.Monday + 7) % 7;
        mondayDate = mondayDate.AddDays(-diff);
        var sundayDate = mondayDate.AddDays(6);

        var fileBytes = await ExcelExporter.GenerateExcelExportAsync(
            userId,
            mondayDate,
            sundayDate,
            $"Wochenbericht KW {System.Globalization.ISOWeek.GetWeekOfYear(new DateTime(mondayDate.Year, mondayDate.Month, mondayDate.Day))}",
            trackerService,
            db);

        return Results.File(
            fileBytes,
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            $"Wochenbericht_KW_{System.Globalization.ISOWeek.GetWeekOfYear(new DateTime(mondayDate.Year, mondayDate.Month, mondayDate.Day))}_{mondayDate:yyyyMMdd}.xlsx");
    }
    return Results.BadRequest("Ungültiges Datum.");
});

trackerApi.MapGet("/export/month", async (ClaimsPrincipal claimsPrincipal, [FromQuery] int year, [FromQuery] int month, ITimetrackerService trackerService, TimetrackerDbContext db) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    if (month < 1 || month > 12) return Results.BadRequest("Ungültiger Monat.");

    var startDate = new DateOnly(year, month, 1);
    var endDate = startDate.AddMonths(1).AddDays(-1);

    var deCulture = new System.Globalization.CultureInfo("de-DE");
    var monthName = deCulture.DateTimeFormat.GetMonthName(month);

    var fileBytes = await ExcelExporter.GenerateExcelExportAsync(
        userId,
        startDate,
        endDate,
        $"Monatsbericht {monthName} {year}",
        trackerService,
        db);

    return Results.File(
        fileBytes,
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        $"Monatsbericht_{monthName}_{year}.xlsx");
});

trackerApi.MapGet("/export/year", async (ClaimsPrincipal claimsPrincipal, [FromQuery] int year, ITimetrackerService trackerService, TimetrackerDbContext db) =>
{
    var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();

    var startDate = new DateOnly(year, 1, 1);
    var endDate = new DateOnly(year, 12, 31);

    var fileBytes = await ExcelExporter.GenerateExcelExportAsync(
        userId,
        startDate,
        endDate,
        $"Jahresbericht {year}",
        trackerService,
        db);

    return Results.File(
        fileBytes,
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        $"Jahresbericht_{year}.xlsx");
});

// ── Holiday-API-Endpoints (Protected) ─────────────────────────────────────────
var holidaysApi = app.MapGroup("/api/holidays").RequireAuthorization();

holidaysApi.MapGet("/", async ([FromQuery] int year, [FromQuery] string? stateCode, IHolidayService holidayService) =>
{
    var list = await holidayService.GetHolidaysAsync(year, stateCode);
    return Results.Ok(list);
});

holidaysApi.MapPost("/fetch/{year:int}", async (int year, IHolidayService holidayService) =>
{
    var (success, message) = await holidayService.FetchAndStoreAsync(year);
    return Results.Ok(new { Success = success, Message = message });
});

holidaysApi.MapDelete("/{id:int}", async (int id, IHolidayService holidayService) =>
{
    await holidayService.DeleteAsync(id);
    return Results.NoContent();
});

app.MapGet("/api/tenant/current", async (ITenantProvider tenantProvider) =>
{
    var tenant = await tenantProvider.GetCurrentTenantAsync();
    if (tenant == null)
    {
        return Results.Ok(new TenantInfo { IsTenant = false });
    }
    return Results.Ok(new TenantInfo
    {
        IsTenant = true,
        Name = tenant.Name,
        Subdomain = tenant.Subdomain,
        IsApproved = tenant.IsApproved,
        PrimaryColor = tenant.PrimaryColor,
        SecondaryColor = tenant.SecondaryColor
    });
});

app.MapPost("/api/auth/register-firm", async ([FromBody] RegisterFirmRequest req, IAuthService authService) =>
{
    var (tenant, error) = await authService.RegisterFirmAsync(req);
    if (tenant == null)
        return Results.BadRequest(error ?? "Registrierung der Firma fehlgeschlagen.");

    return Results.Ok(new { Success = true });
}).RequireRateLimiting("auth-limit");

// ── SuperAdmin-API-Endpoints (Protected) ───────────────────────────────────────────
var superAdminApi = app.MapGroup("/api/superadmin").RequireAuthorization("AdminOnly");

superAdminApi.MapGet("/tenants", async (TimetrackerDbContext db) =>
{
    var tenants = await db.Tenants
        .OrderByDescending(t => t.CreatedAt)
        .ToListAsync();

    var stats = new List<object>();
    foreach (var t in tenants)
    {
        var userCount = await db.Users.IgnoreQueryFilters().CountAsync(u => u.TenantId == t.Id);
        var workDaysCount = await db.WorkDays.IgnoreQueryFilters().CountAsync(w => w.TenantId == t.Id);
        var tenantUsers = await db.Users.IgnoreQueryFilters()
            .Where(u => u.TenantId == t.Id)
            .Select(u => new { u.Id, u.Username, u.IsTenantAdmin })
            .ToListAsync();

        stats.Add(new
        {
            t.Id,
            t.Name,
            t.Subdomain,
            t.IsApproved,
            t.CreatedAt,
            UserCount = userCount,
            WorkDaysCount = workDaysCount,
            Users = tenantUsers
        });
    }
    return Results.Ok(stats);
});

superAdminApi.MapPost("/tenants/{id:int}/approve", async (int id, TimetrackerDbContext db) =>
{
    var tenant = await db.Tenants.FindAsync(id);
    if (tenant == null) return Results.NotFound();
    tenant.IsApproved = true;
    await db.SaveChangesAsync();
    return Results.Ok();
});

superAdminApi.MapPost("/tenants/{id:int}/toggle-active", async (int id, TimetrackerDbContext db) =>
{
    var tenant = await db.Tenants.FindAsync(id);
    if (tenant == null) return Results.NotFound();
    tenant.IsApproved = !tenant.IsApproved;
    await db.SaveChangesAsync();
    return Results.Ok();
});

superAdminApi.MapDelete("/tenants/{id:int}", async (int id, TimetrackerDbContext db, UserNotificationService notifier) =>
{
    var tenant = await db.Tenants.FindAsync(id);
    if (tenant == null) return Results.NotFound();

    await using var transaction = await db.Database.BeginTransactionAsync();
    try
    {
        var userIds = await db.Users.IgnoreQueryFilters()
            .Where(u => u.TenantId == id)
            .Select(u => u.Id)
            .ToListAsync();

        var workDays = await db.WorkDays.IgnoreQueryFilters().Where(w => w.TenantId == id).ToListAsync();
        db.WorkDays.RemoveRange(workDays);

        var vacationDays = await db.VacationDays.IgnoreQueryFilters().Where(v => v.TenantId == id).ToListAsync();
        db.VacationDays.RemoveRange(vacationDays);

        var settings = await db.AppSettings.IgnoreQueryFilters().Where(s => s.TenantId == id).ToListAsync();
        db.AppSettings.RemoveRange(settings);

        var users = await db.Users.IgnoreQueryFilters().Where(u => u.TenantId == id).ToListAsync();
        db.Users.RemoveRange(users);

        db.Tenants.Remove(tenant);

        await db.SaveChangesAsync();
        await transaction.CommitAsync();

        foreach (var uid in userIds)
        {
            await notifier.NotifyUserDeletedAsync(uid);
        }
        await notifier.NotifyUsersChangedAsync();

        return Results.NoContent();
    }
    catch (Exception ex)
    {
        await transaction.RollbackAsync();
        return Results.BadRequest($"Fehler beim Löschen der Firma: {ex.Message}");
    }
});

app.Run();

static (string Hash, string Salt) HashPassword(string password)
{
    var saltBytes = RandomNumberGenerator.GetBytes(32);
    var salt = Convert.ToBase64String(saltBytes);
    var hash = ComputePasswordHash(password, salt);
    return (hash, salt);
}

static bool IsLegacySeedPassword(string hash, string salt, string password)
{
    var passwordBytes = Encoding.UTF8.GetBytes(password + salt);
    var legacyHash = Convert.ToBase64String(SHA256.HashData(passwordBytes));
    return hash == legacyHash;
}

static string ComputePasswordHash(string password, string salt)
{
    var hash = Rfc2898DeriveBytes.Pbkdf2(
        Encoding.UTF8.GetBytes(password),
        Convert.FromBase64String(salt),
        iterations: 200_000,
        HashAlgorithmName.SHA256,
        outputLength: 32);
    return Convert.ToBase64String(hash);
}

static async Task EnsurePostgresIntegerPrimaryKeyDefaultAsync(System.Data.Common.DbConnection conn, string tableName)
{
    using var cmd = conn.CreateCommand();
    cmd.CommandText = $@"
DO $$
BEGIN
    IF EXISTS (
        SELECT 1
        FROM information_schema.columns
        WHERE table_schema = 'public'
          AND table_name = '{tableName}'
          AND column_name = 'Id'
    ) THEN
        EXECUTE 'CREATE SEQUENCE IF NOT EXISTS public.""{tableName}_Id_seq""';
        EXECUTE 'ALTER SEQUENCE public.""{tableName}_Id_seq"" OWNED BY public.""{tableName}"".""Id""';
        EXECUTE 'ALTER TABLE public.""{tableName}"" ALTER COLUMN ""Id"" SET DEFAULT nextval(''''public.""{tableName}_Id_seq""'''')';
        EXECUTE 'SELECT setval(''''public.""{tableName}_Id_seq""'''', COALESCE((SELECT MAX(""Id"") FROM public.""{tableName}""), 0) + 1, false)';
    END IF;
END $$;";
    await cmd.ExecuteNonQueryAsync();
}

// ── Models for Request Bodies ──────────────────────────────────────────────────
public record LoginRequest(string Username, string Password);
public record RegisterRequest(string Username, string Password, string? Honeypot = null);
public record RenameRequest(string Username);
public record ChangePasswordRequest(string CurrentPassword, string NewPassword);
