using System.Security.Claims;
using System.Text.RegularExpressions;
using GmRelay.Shared.Domain;

namespace GmRelay.Web.Services;

public sealed class AuthorizedSessionService(ISessionStore sessionStore, IHttpContextAccessor httpContextAccessor)
{
    private (string Platform, string ExternalUserId, string Name)? GetCurrentIdentity()
    {
        var user = httpContextAccessor.HttpContext?.User;
        if (user is null || !user.TryGetPlatformIdentity(out var platform, out var externalUserId))
            return null;

        var name = user.FindFirst(ClaimTypes.Name)?.Value ?? externalUserId;
        return (platform, externalUserId, name);
    }

    public Task<List<WebGameGroup>> GetGroupsForCurrentUserAsync()
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return Task.FromResult(new List<WebGameGroup>());

        return sessionStore.GetGroupsForUserAsync(identity.Value.Platform, identity.Value.ExternalUserId);
    }

    public async Task<WebGroupManagement?> GetGroupManagementForCurrentUserAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return null;

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
            return null;

        var group = await sessionStore.GetGroupAsync(groupId);
        if (group is null)
            return null;

        var managers = await sessionStore.GetGroupManagersAsync(groupId);
        var isOwner = await sessionStore.IsGroupOwnerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId);
        return new WebGroupManagement(group, managers, isOwner);
    }

    public async Task<List<WebSession>?> GetUpcomingSessionsForCurrentUserAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return null;

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
            return null;

        return await sessionStore.GetUpcomingSessionsAsync(groupId);
    }

    public async Task<WebPublicGroupSettings?> GetPublicGroupSettingsForCurrentUserAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return null;

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
            return null;

        return await sessionStore.GetPublicGroupSettingsAsync(groupId);
    }

    public async Task UpdatePublicGroupSettingsForCurrentUserAsync(
        Guid groupId,
        string? publicSlug,
        bool publicScheduleEnabled)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
        {
            throw new SessionAccessDeniedException(groupId, identity.Value.ExternalUserId);
        }

        var normalizedSlug = NormalizePublicSlug(publicSlug);
        if (publicScheduleEnabled && normalizedSlug is null)
        {
            throw new InvalidOperationException("Для публичной страницы нужен короткий адрес.");
        }

        await sessionStore.UpdatePublicGroupSettingsAsync(groupId, normalizedSlug, publicScheduleEnabled);
    }

    public Task<MasterProfileSettings?> GetMasterProfileSettingsForCurrentUserAsync()
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return Task.FromResult<MasterProfileSettings?>(null);

        return sessionStore.GetMasterProfileSettingsAsync(identity.Value.Platform, identity.Value.ExternalUserId);
    }

    public async Task UpdateMasterProfileSettingsForCurrentUserAsync(
        string? publicSlug,
        bool isPublic,
        string displayName,
        string? bio)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var normalizedDisplayName = displayName.Trim();
        if (normalizedDisplayName.Length is < 2 or > 120)
        {
            throw new InvalidOperationException("Имя профиля должно быть от 2 до 120 символов.");
        }

        var normalizedBio = string.IsNullOrWhiteSpace(bio) ? null : bio.Trim();
        if (normalizedBio?.Length > 1200)
        {
            throw new InvalidOperationException("Описание профиля должно быть не длиннее 1200 символов.");
        }

        var normalizedSlug = NormalizeMasterProfileSlug(publicSlug);
        if (isPublic && normalizedSlug is null)
        {
            throw new InvalidOperationException("Для публичного профиля нужен короткий адрес.");
        }

        await sessionStore.UpdateMasterProfileSettingsAsync(
            identity.Value.Platform,
            identity.Value.ExternalUserId,
            normalizedSlug,
            isPublic,
            normalizedDisplayName,
            normalizedBio);
    }

    public async Task SetSessionPublicationModeForCurrentUserAsync(Guid sessionId, PublicationMode mode)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var session = await GetSessionForCurrentUserAsync(sessionId);
        if (session is null)
        {
            throw new SessionAccessDeniedException(sessionId, identity.Value.ExternalUserId);
        }

        await sessionStore.SetSessionPublicationModeAsync(sessionId, session.GroupId, mode);
    }

    public async Task SetBatchPublicationModeForCurrentUserAsync(Guid batchId, PublicationMode mode)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var batch = await GetBatchForCurrentUserAsync(batchId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, identity.Value.ExternalUserId);
        }

        await sessionStore.SetBatchPublicationModeAsync(batchId, batch.GroupId, mode);
    }

    public async Task<bool> IsActiveClubMemberForCurrentUserAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return false;

        var playerId = await sessionStore.GetPlayerIdByPlatformIdentityAsync(identity.Value.Platform, identity.Value.ExternalUserId);
        if (playerId is null)
            return false;

        return await sessionStore.IsActiveClubMemberAsync(groupId, playerId.Value);
    }

    public async Task<WebSession?> GetSessionForCurrentUserAsync(Guid sessionId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return null;

        var session = await sessionStore.GetSessionAsync(sessionId);
        if (session is null)
            return null;

        return await sessionStore.IsGroupManagerAsync(session.GroupId, identity.Value.Platform, identity.Value.ExternalUserId) ? session : null;
    }

    public async Task<WebSessionBatch?> GetBatchForCurrentUserAsync(Guid batchId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return null;

        var batch = await sessionStore.GetBatchAsync(batchId);
        if (batch is null)
            return null;

        return await sessionStore.IsGroupManagerAsync(batch.GroupId, identity.Value.Platform, identity.Value.ExternalUserId) ? batch : null;
    }

    public async Task UpdateSessionForCurrentUserAsync(Guid sessionId, string title, DateTime scheduledAt, string joinLink, int? maxPlayers)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var session = await GetSessionForCurrentUserAsync(sessionId);
        if (session is null)
        {
            throw new SessionAccessDeniedException(sessionId, identity.Value.ExternalUserId);
        }

        await sessionStore.UpdateSessionAsync(sessionId, session.GroupId, title, scheduledAt, joinLink, maxPlayers);

        if (session.Title != title)
            await sessionStore.LogSessionChangeAsync(sessionId, identity.Value.ExternalUserId, identity.Value.Name, "Title", session.Title, title);
        if (session.ScheduledAt != scheduledAt)
            await sessionStore.LogSessionChangeAsync(sessionId, identity.Value.ExternalUserId, identity.Value.Name, "Time", session.ScheduledAt.ToString("O"), scheduledAt.ToString("O"));
        if (session.JoinLink != joinLink)
            await sessionStore.LogSessionChangeAsync(sessionId, identity.Value.ExternalUserId, identity.Value.Name, "Link", session.JoinLink, joinLink);
        if (session.MaxPlayers != maxPlayers)
            await sessionStore.LogSessionChangeAsync(sessionId, identity.Value.ExternalUserId, identity.Value.Name, "MaxPlayers", session.MaxPlayers?.ToString(), maxPlayers?.ToString());
    }

    public async Task PromoteWaitlistedPlayerForCurrentUserAsync(Guid sessionId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var session = await GetSessionForCurrentUserAsync(sessionId);
        if (session is null)
        {
            throw new SessionAccessDeniedException(sessionId, identity.Value.ExternalUserId);
        }

        await sessionStore.PromoteWaitlistedPlayerAsync(sessionId, session.GroupId);
        await sessionStore.LogSessionChangeAsync(sessionId, identity.Value.ExternalUserId, identity.Value.Name, "WaitlistPromote", null, null);
    }

    public async Task UpdateBatchDetailsForCurrentUserAsync(Guid batchId, string title, string joinLink)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var batch = await GetBatchForCurrentUserAsync(batchId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, identity.Value.ExternalUserId);
        }

        await sessionStore.UpdateBatchDetailsAsync(batchId, batch.GroupId, title.Trim(), joinLink.Trim());
    }

    public async Task UpdateBatchNotificationModeForCurrentUserAsync(Guid batchId, SessionNotificationMode notificationMode)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var batch = await GetBatchForCurrentUserAsync(batchId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, identity.Value.ExternalUserId);
        }

        await sessionStore.UpdateBatchNotificationModeAsync(batchId, batch.GroupId, notificationMode);
    }

    public async Task RescheduleBatchForCurrentUserAsync(Guid batchId, DateTime firstScheduledAt, int intervalDays)
    {
        if (intervalDays <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(intervalDays), intervalDays, "Interval must be greater than zero.");
        }

        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var batch = await GetBatchForCurrentUserAsync(batchId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, identity.Value.ExternalUserId);
        }

        await sessionStore.RescheduleBatchAsync(batchId, batch.GroupId, firstScheduledAt, intervalDays);
        await sessionStore.LogSessionChangeAsync(batchId, identity.Value.ExternalUserId, identity.Value.Name, "BatchRescheduled", batch.FirstScheduledAt.ToString("O"), firstScheduledAt.ToString("O"));
    }

    public async Task<WebSessionBatch> CloneBatchForCurrentUserAsync(Guid batchId, BatchCloneInterval interval)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var batch = await GetBatchForCurrentUserAsync(batchId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, identity.Value.ExternalUserId);
        }

        return await sessionStore.CloneBatchAsync(batchId, batch.GroupId, interval);
    }

    public async Task<List<WebCampaignTemplate>?> GetCampaignTemplatesForCurrentUserAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return null;

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
            return null;

        return await sessionStore.GetCampaignTemplatesAsync(groupId);
    }

    public async Task<WebCampaignTemplate> CreateCampaignTemplateForCurrentUserAsync(
        Guid groupId,
        CreateCampaignTemplateRequest request)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
        {
            throw new SessionAccessDeniedException(groupId, identity.Value.ExternalUserId);
        }

        var normalizedRequest = NormalizeCampaignTemplateRequest(request);
        return await sessionStore.CreateCampaignTemplateAsync(groupId, normalizedRequest);
    }

    public async Task DeleteCampaignTemplateForCurrentUserAsync(Guid templateId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var template = await sessionStore.GetCampaignTemplateAsync(templateId);
        if (template is null || !await sessionStore.IsGroupManagerAsync(template.GroupId, identity.Value.Platform, identity.Value.ExternalUserId))
        {
            throw new SessionAccessDeniedException(templateId, identity.Value.ExternalUserId);
        }

        await sessionStore.DeleteCampaignTemplateAsync(templateId, template.GroupId);
    }

    public async Task<WebSessionBatch> CreateBatchFromCampaignTemplateForCurrentUserAsync(
        Guid templateId,
        DateTime firstScheduledAt)
    {
        if (firstScheduledAt <= DateTime.UtcNow)
        {
            throw new ArgumentOutOfRangeException(nameof(firstScheduledAt), firstScheduledAt, "First scheduled time must be in the future.");
        }

        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var template = await sessionStore.GetCampaignTemplateAsync(templateId);
        if (template is null || !await sessionStore.IsGroupManagerAsync(template.GroupId, identity.Value.Platform, identity.Value.ExternalUserId))
        {
            throw new SessionAccessDeniedException(templateId, identity.Value.ExternalUserId);
        }

        return await sessionStore.CreateBatchFromTemplateAsync(templateId, template.GroupId, firstScheduledAt);
    }

    public async Task AddCoGmForOwnerAsync(Guid groupId, string coGmPlatform, string coGmExternalUserId, string displayName, string? externalUsername)
    {
        if (string.IsNullOrWhiteSpace(coGmExternalUserId))
        {
            throw new ArgumentException("External user id must not be empty.", nameof(coGmExternalUserId));
        }

        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        if (identity.Value.ExternalUserId == coGmExternalUserId && identity.Value.Platform == coGmPlatform)
        {
            throw new InvalidOperationException("Owner is already a group manager.");
        }

        if (!await sessionStore.IsGroupOwnerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
        {
            throw new SessionAccessDeniedException(groupId, identity.Value.ExternalUserId);
        }

        var normalizedName = string.IsNullOrWhiteSpace(displayName)
            ? $"{coGmPlatform} {coGmExternalUserId}"
            : displayName.Trim();
        var normalizedUsername = string.IsNullOrWhiteSpace(externalUsername)
            ? null
            : externalUsername.Trim().TrimStart('@');

        await sessionStore.AddGroupCoGmAsync(
            groupId,
            identity.Value.Platform, identity.Value.ExternalUserId,
            coGmPlatform, coGmExternalUserId,
            normalizedName, normalizedUsername);
    }

    public async Task RemoveCoGmForOwnerAsync(Guid groupId, string coGmPlatform, string coGmExternalUserId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        if (!await sessionStore.IsGroupOwnerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
        {
            throw new SessionAccessDeniedException(groupId, identity.Value.ExternalUserId);
        }

        await sessionStore.RemoveGroupCoGmAsync(groupId, coGmPlatform, coGmExternalUserId);
    }

    public async Task<List<WebParticipant>?> GetSessionParticipantsForCurrentUserAsync(Guid sessionId)
    {
        var session = await GetSessionForCurrentUserAsync(sessionId);
        if (session is null)
            return null;

        return await sessionStore.GetSessionParticipantsAsync(sessionId);
    }

    public async Task<List<SessionAuditLogEntry>?> GetSessionHistoryForCurrentUserAsync(Guid sessionId)
    {
        var session = await GetSessionForCurrentUserAsync(sessionId);
        if (session is null)
            return null;

        return await sessionStore.GetSessionHistoryAsync(sessionId);
    }

    public async Task RemovePlayerFromSessionForCurrentUserAsync(Guid sessionId, Guid participantId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            throw new InvalidOperationException("User is not authenticated.");

        var session = await GetSessionForCurrentUserAsync(sessionId);
        if (session is null)
        {
            throw new SessionAccessDeniedException(sessionId, identity.Value.ExternalUserId);
        }

        await sessionStore.RemovePlayerFromSessionAsync(sessionId, session.GroupId, participantId);
        await sessionStore.LogSessionChangeAsync(sessionId, identity.Value.ExternalUserId, identity.Value.Name, "PlayerRemoved", participantId.ToString(), null);
    }

    public async Task<List<PlayerAttendanceStats>?> GetGroupAttendanceStatsForCurrentUserAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
            return null;

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
            return null;

        return await sessionStore.GetGroupAttendanceStatsAsync(groupId);
    }

    private static CreateCampaignTemplateRequest NormalizeCampaignTemplateRequest(CreateCampaignTemplateRequest request)
    {
        var name = request.Name.Trim();
        var title = request.Title.Trim();
        var joinLink = request.JoinLink.Trim();

        if (name.Length == 0)
        {
            throw new ArgumentException("Template name must not be empty.", nameof(request));
        }

        if (title.Length == 0)
        {
            throw new ArgumentException("Session title must not be empty.", nameof(request));
        }

        if (joinLink.Length == 0)
        {
            throw new ArgumentException("Join link must not be empty.", nameof(request));
        }

        if (request.SessionCount is < 1 or > 52)
        {
            throw new ArgumentOutOfRangeException(nameof(request), request.SessionCount, "Session count must be between 1 and 52.");
        }

        if (request.IntervalDays is < 1 or > 365)
        {
            throw new ArgumentOutOfRangeException(nameof(request), request.IntervalDays, "Interval must be between 1 and 365 days.");
        }

        if (request.MaxPlayers is <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(request), request.MaxPlayers, "Seat limit must be greater than zero.");
        }

        return request with
        {
            Name = name,
            Title = title,
            JoinLink = joinLink
        };
    }

    private static string? NormalizePublicSlug(string? publicSlug)
    {
        if (string.IsNullOrWhiteSpace(publicSlug))
        {
            return null;
        }

        var slug = Regex.Replace(publicSlug.Trim().ToLowerInvariant(), @"[\s_]+", "-").Trim('-');
        if (slug.Length is < 3 or > 80 || !Regex.IsMatch(slug, "^[a-z0-9]+(?:-[a-z0-9]+)*$"))
        {
            throw new InvalidOperationException("Короткий адрес может содержать только латинские буквы, цифры и дефисы.");
        }

        return slug;
    }

    private static string? NormalizeMasterProfileSlug(string? publicSlug) => NormalizePublicSlug(publicSlug);
}
