using Backend.Common;
using Backend.Contracts;
using Backend.Data;
using Backend.Domain;
using Backend.Repositories;
using Backend.Security;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;

namespace Backend.Services;

public sealed class UserSessionService(IUserSessionRepository userSessionRepository, AwardsDbContext db) : IUserSessionService
{
    public const int MinimumIdleTimeoutHours = 3;
    public const int DefaultIdleTimeoutHours = 3;
    private static readonly TimeSpan AbsoluteSessionLifetime = TimeSpan.FromDays(30);

    public async Task<UserSession?> ResolveSessionAsync(HttpContext context, CancellationToken cancellationToken = default)
    {
        var token = ReadBearerToken(context);
        if (string.IsNullOrWhiteSpace(token))
        {
            return null;
        }

        var session = await userSessionRepository.GetActiveByTokenAsync(token, cancellationToken);
        if (session is null)
        {
            return null;
        }

        var now = DateTimeOffset.UtcNow;
        var idleSessionLifetime = await ResolveIdleSessionLifetimeAsync(cancellationToken);
        if (IsExpired(session, now, idleSessionLifetime))
        {
            session.IsActive = false;
            await userSessionRepository.SaveChangesAsync(cancellationToken);
            return null;
        }

        session.LastSeenAt = now;
        await userSessionRepository.SaveChangesAsync(cancellationToken);
        context.SetCurrentSession(session);
        return session;
    }

    public async Task<int> GetIdleTimeoutHoursAsync(CancellationToken cancellationToken = default)
    {
        var configuredHours = await db.SiteSettings
            .AsNoTracking()
            .Where(item => item.Id == 1)
            .Select(item => (int?)item.SessionIdleTimeoutHours)
            .FirstOrDefaultAsync(cancellationToken);

        return NormalizeIdleTimeoutHours(configuredHours);
    }

    public Task<UserSession> CreateDevSessionAsync(LoginRequest request, RequestMetadata metadata, CancellationToken cancellationToken = default)
    {
        return CreateSessionAsync(
            request.TwitchUserId,
            request.DisplayName,
            request.Role,
            metadata,
            cancellationToken);
    }

    public Task<UserSession> CreateSessionAsync(
        string twitchUserId,
        string displayName,
        string role,
        RequestMetadata metadata,
        CancellationToken cancellationToken = default)
    {
        var normalizedTwitchUserId = twitchUserId.Trim();
        var normalizedDisplayName = displayName.Trim();
        var session = new UserSession
        {
            Id = Guid.NewGuid(),
            SessionToken = Convert.ToHexString(RandomNumberGenerator.GetBytes(32)).ToLowerInvariant(),
            TwitchUserId = normalizedTwitchUserId,
            DisplayName = normalizedDisplayName,
            Role = AdminRoles.Normalize(role),
            CreatedFromIp = metadata.ClientIp,
            UserAgent = metadata.UserAgent,
            CreatedAt = DateTimeOffset.UtcNow,
            LastSeenAt = DateTimeOffset.UtcNow,
            IsActive = true,
        };

        userSessionRepository.Add(session);
        return PersistAndReturnAsync(session, cancellationToken);
    }

    public Task<int> CountRecentSessionsFromIpAsync(string ipAddress, DateTimeOffset since, CancellationToken cancellationToken = default) =>
        userSessionRepository.CountRecentSessionsFromIpAsync(ipAddress, since, cancellationToken);

    public async Task LogoutAsync(UserSession session, CancellationToken cancellationToken = default)
    {
        session.IsActive = false;
        await userSessionRepository.SaveChangesAsync(cancellationToken);
    }

    private static string? ReadBearerToken(HttpContext context)
    {
        var header = context.Request.Headers.Authorization.ToString();
        return header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
            ? header["Bearer ".Length..].Trim()
            : null;
    }

    private async Task<TimeSpan> ResolveIdleSessionLifetimeAsync(CancellationToken cancellationToken)
    {
        var idleTimeoutHours = await GetIdleTimeoutHoursAsync(cancellationToken);
        return TimeSpan.FromHours(idleTimeoutHours);
    }

    public static int NormalizeIdleTimeoutHours(int? configuredHours) =>
        Math.Max(MinimumIdleTimeoutHours, configuredHours ?? DefaultIdleTimeoutHours);

    private static bool IsExpired(UserSession session, DateTimeOffset now, TimeSpan idleSessionLifetime) =>
        session.CreatedAt <= now.Subtract(AbsoluteSessionLifetime)
        || session.LastSeenAt <= now.Subtract(idleSessionLifetime);

    private async Task<UserSession> PersistAndReturnAsync(UserSession session, CancellationToken cancellationToken)
    {
        await userSessionRepository.SaveChangesAsync(cancellationToken);
        return session;
    }
}
