using GmRelay.Shared.Domain;

namespace GmRelay.Web.Services;

public sealed class AuthorizedSessionService(ISessionStore sessionStore)
{
    public Task<List<WebGameGroup>> GetGroupsForGmAsync(long gmId) =>
        sessionStore.GetGroupsForGmAsync(gmId);

    public async Task<WebGroupManagement?> GetGroupManagementForGmAsync(Guid groupId, long gmId)
    {
        if (!await GroupBelongsToGmAsync(groupId, gmId))
        {
            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, gmId);
        return new WebGroupManagement(group, managers, isOwner);
    }

    public async Task<List<WebSession>?> GetUpcomingSessionsForGmAsync(Guid groupId, long gmId)
    {
        if (!await GroupBelongsToGmAsync(groupId, gmId))
        {
            return null;
        }

        return await sessionStore.GetUpcomingSessionsAsync(groupId);
    }

    public async Task<WebSession?> GetSessionForGmAsync(Guid sessionId, long gmId)
    {
        var session = await sessionStore.GetSessionAsync(sessionId);
        if (session is null)
        {
            return null;
        }

        return await GroupBelongsToGmAsync(session.GroupId, gmId) ? session : null;
    }

    public async Task<WebSessionBatch?> GetBatchForGmAsync(Guid batchId, long gmId)
    {
        var batch = await sessionStore.GetBatchAsync(batchId);
        if (batch is null)
        {
            return null;
        }

        return await GroupBelongsToGmAsync(batch.GroupId, gmId) ? batch : null;
    }

    public async Task UpdateSessionForGmAsync(Guid sessionId, long gmId, string title, DateTime scheduledAt, string joinLink, int? maxPlayers)
    {
        var session = await GetSessionForGmAsync(sessionId, gmId);
        if (session is null)
        {
            throw new SessionAccessDeniedException(sessionId, gmId);
        }

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

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

    public async Task PromoteWaitlistedPlayerForGmAsync(Guid sessionId, long gmId)
    {
        var session = await GetSessionForGmAsync(sessionId, gmId);
        if (session is null)
        {
            throw new SessionAccessDeniedException(sessionId, gmId);
        }

        await sessionStore.PromoteWaitlistedPlayerAsync(sessionId, session.GroupId);
        await sessionStore.LogSessionChangeAsync(sessionId, gmId, "ГМ", "WaitlistPromote", null, null);
    }

    public async Task UpdateBatchDetailsForGmAsync(Guid batchId, long gmId, string title, string joinLink)
    {
        var batch = await GetBatchForGmAsync(batchId, gmId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, gmId);
        }

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

    public async Task UpdateBatchNotificationModeForGmAsync(Guid batchId, long gmId, SessionNotificationMode notificationMode)
    {
        var batch = await GetBatchForGmAsync(batchId, gmId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, gmId);
        }

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

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

        var batch = await GetBatchForGmAsync(batchId, gmId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, gmId);
        }

        await sessionStore.RescheduleBatchAsync(batchId, batch.GroupId, firstScheduledAt, intervalDays);
        await sessionStore.LogSessionChangeAsync(batchId, gmId, "ГМ", "BatchRescheduled", batch.FirstScheduledAt.ToString("O"), firstScheduledAt.ToString("O"));
    }

    public async Task<WebSessionBatch> CloneBatchForGmAsync(Guid batchId, long gmId, BatchCloneInterval interval)
    {
        var batch = await GetBatchForGmAsync(batchId, gmId);
        if (batch is null)
        {
            throw new SessionAccessDeniedException(batchId, gmId);
        }

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

    public async Task<List<WebCampaignTemplate>?> GetCampaignTemplatesForGmAsync(Guid groupId, long gmId)
    {
        if (!await GroupBelongsToGmAsync(groupId, gmId))
        {
            return null;
        }

        return await sessionStore.GetCampaignTemplatesAsync(groupId);
    }

    public async Task<WebCampaignTemplate> CreateCampaignTemplateForGmAsync(
        Guid groupId,
        long gmId,
        CreateCampaignTemplateRequest request)
    {
        if (!await GroupBelongsToGmAsync(groupId, gmId))
        {
            throw new SessionAccessDeniedException(groupId, gmId);
        }

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

    public async Task DeleteCampaignTemplateForGmAsync(Guid templateId, long gmId)
    {
        var template = await sessionStore.GetCampaignTemplateAsync(templateId);
        if (template is null || !await GroupBelongsToGmAsync(template.GroupId, gmId))
        {
            throw new SessionAccessDeniedException(templateId, gmId);
        }

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

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

        var template = await sessionStore.GetCampaignTemplateAsync(templateId);
        if (template is null || !await GroupBelongsToGmAsync(template.GroupId, gmId))
        {
            throw new SessionAccessDeniedException(templateId, gmId);
        }

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

    public async Task AddCoGmForOwnerAsync(Guid groupId, long ownerTelegramId, long coGmTelegramId, string displayName, string? telegramUsername)
    {
        if (coGmTelegramId <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(coGmTelegramId), coGmTelegramId, "Telegram id must be greater than zero.");
        }

        if (ownerTelegramId == coGmTelegramId)
        {
            throw new InvalidOperationException("Owner is already a group manager.");
        }

        if (!await sessionStore.IsGroupOwnerAsync(groupId, ownerTelegramId))
        {
            throw new SessionAccessDeniedException(groupId, ownerTelegramId);
        }

        var normalizedName = string.IsNullOrWhiteSpace(displayName)
            ? $"Telegram {coGmTelegramId.ToString(System.Globalization.CultureInfo.InvariantCulture)}"
            : displayName.Trim();
        var normalizedUsername = string.IsNullOrWhiteSpace(telegramUsername)
            ? null
            : telegramUsername.Trim().TrimStart('@');

        await sessionStore.AddGroupCoGmAsync(groupId, ownerTelegramId, coGmTelegramId, normalizedName, normalizedUsername);
    }

    public async Task RemoveCoGmForOwnerAsync(Guid groupId, long ownerTelegramId, long coGmTelegramId)
    {
        if (!await sessionStore.IsGroupOwnerAsync(groupId, ownerTelegramId))
        {
            throw new SessionAccessDeniedException(groupId, ownerTelegramId);
        }

        await sessionStore.RemoveGroupCoGmAsync(groupId, coGmTelegramId);
    }

    public async Task<List<WebParticipant>?> GetSessionParticipantsForGmAsync(Guid sessionId, long gmId)
    {
        var session = await GetSessionForGmAsync(sessionId, gmId);
        if (session is null)
        {
            return null;
        }

        return await sessionStore.GetSessionParticipantsAsync(sessionId);
    }

    public async Task<List<SessionAuditLogEntry>?> GetSessionHistoryForGmAsync(Guid sessionId, long gmId)
    {
        var session = await GetSessionForGmAsync(sessionId, gmId);
        if (session is null)
        {
            return null;
        }

        return await sessionStore.GetSessionHistoryAsync(sessionId);
    }

    public async Task RemovePlayerFromSessionForGmAsync(Guid sessionId, long gmId, Guid participantId)
    {
        var session = await GetSessionForGmAsync(sessionId, gmId);
        if (session is null)
        {
            throw new SessionAccessDeniedException(sessionId, gmId);
        }

        await sessionStore.RemovePlayerFromSessionAsync(sessionId, session.GroupId, participantId);
        await sessionStore.LogSessionChangeAsync(sessionId, gmId, "ГМ", "PlayerRemoved", participantId.ToString(), null);
    }

    private async Task<bool> GroupBelongsToGmAsync(Guid groupId, long gmId)
    {
        return await sessionStore.IsGroupManagerAsync(groupId, gmId);
    }

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