using System.Security.Claims;
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 timetracker.Server.Components;
using timetracker.Data;
using timetracker.Shared;

var builder = WebApplication.CreateBuilder(args);

// 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();

// 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.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));
}
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}"));
}

var app = builder.Build();

// 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))
    {
        await db.Database.EnsureCreatedAsync();
    }
    else
    {
        await db.Database.MigrateAsync();
    }
}

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 ?? "";
        if (int.TryParse(idClaim, out var id))
        {
            return Results.Ok(new UserInfo { Id = id, Username = name });
        }
    }
    return Results.Unauthorized();
});

app.MapPost("/api/auth/login", async (HttpContext ctx, [FromBody] LoginRequest req, IAuthService authService) =>
{
    var user = await authService.LoginAsync(req.Username, req.Password);
    if (user == null)
        return Results.BadRequest("Benutzername oder Passwort falsch.");

    var claims = new[] {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.Username)
    };
    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 });
}).RequireRateLimiting("auth-limit");

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

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

    var claims = new[] {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.Username)
    };
    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 });
}).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();
});

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

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);
});

// ── 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.Run();

// ── 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);
