using System.Security.Claims;
using GmRelay.Web.Services.Portfolio.Covers;

namespace GmRelay.Web.Services.Portfolio;

public sealed class AuthorizedPortfolioService(
    IPortfolioStore portfolioStore,
    ISessionStore sessionStore,
    IPortfolioCoverStorage coverStorage,
    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;
        return (platform, externalUserId, name);
    }

    private async Task<(string Platform, string ExternalUserId)> RequireManagerAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
        {
            throw new SessionAccessDeniedException(groupId, "<anonymous>");
        }

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

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

    private async Task<(Guid GroupId, string Platform, string ExternalUserId)> RequireManagerForGameAsync(Guid portfolioGameId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
        {
            throw new SessionAccessDeniedException(portfolioGameId, "<anonymous>");
        }

        var groupId = await portfolioStore.GetPortfolioGameGroupIdAsync(portfolioGameId);
        if (groupId is null)
        {
            throw new InvalidOperationException("Portfolio game not found.");
        }

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

        return (groupId.Value, identity.Value.Platform, identity.Value.ExternalUserId);
    }

    // --- Protected reads ---

    public async Task<IReadOnlyList<PortfolioGameSummary>> GetPortfolioGamesForCurrentUserAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
        {
            return [];
        }

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
        {
            return [];
        }

        return await portfolioStore.GetPortfolioGamesForGroupAsync(groupId);
    }

    public async Task<PortfolioGameEditor?> GetPortfolioGameForCurrentUserAsync(Guid portfolioGameId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
        {
            return null;
        }

        var groupId = await portfolioStore.GetPortfolioGameGroupIdAsync(portfolioGameId);
        if (groupId is null)
        {
            return null;
        }

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

        return await portfolioStore.GetPortfolioGameForManagementAsync(portfolioGameId);
    }

    public async Task<IReadOnlyList<PortfolioSessionOption>> GetCompletedSessionsForCurrentUserAsync(Guid groupId)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
        {
            return [];
        }

        if (!await sessionStore.IsGroupManagerAsync(groupId, identity.Value.Platform, identity.Value.ExternalUserId))
        {
            return [];
        }

        return await portfolioStore.GetEligibleCompletedSessionsAsync(groupId, null);
    }

    // --- Protected writes ---

    public async Task<Guid> CreateDraftForCurrentUserAsync(Guid groupId, Guid? preselectedSessionId)
    {
        await RequireManagerAsync(groupId);
        return await portfolioStore.CreatePortfolioDraftAsync(groupId, preselectedSessionId);
    }

    public async Task UpdateDraftForCurrentUserAsync(Guid portfolioGameId, PortfolioGameUpdate update)
    {
        var (groupId, _, _) = await RequireManagerForGameAsync(portfolioGameId);

        var normalized = NormalizeUpdate(update);
        await portfolioStore.UpdatePortfolioDraftAsync(portfolioGameId, groupId, normalized);
    }

    public async Task ReplaceCoverForCurrentUserAsync(
        Guid portfolioGameId,
        Stream content,
        string contentType,
        CancellationToken cancellationToken = default)
    {
        var (groupId, _, _) = await RequireManagerForGameAsync(portfolioGameId);

        var saveResult = await coverStorage.SaveAsync(content, contentType, cancellationToken);
        var newKey = saveResult.StorageKey;

        try
        {
            var oldKey = await portfolioStore.SetPortfolioCoverAsync(portfolioGameId, groupId, newKey);
            if (!string.IsNullOrWhiteSpace(oldKey))
            {
                await coverStorage.DeleteIfExistsAsync(oldKey, cancellationToken);
            }
        }
        catch
        {
            await coverStorage.DeleteIfExistsAsync(newKey, cancellationToken);
            throw;
        }
    }

    public async Task DeleteForCurrentUserAsync(Guid portfolioGameId)
    {
        var (groupId, _, _) = await RequireManagerForGameAsync(portfolioGameId);

        var coverKey = await portfolioStore.DeletePortfolioGameAsync(portfolioGameId, groupId);
        if (!string.IsNullOrWhiteSpace(coverKey))
        {
            await coverStorage.DeleteIfExistsAsync(coverKey);
        }
    }

    public async Task SetPublicationForCurrentUserAsync(Guid portfolioGameId, bool isPublic)
    {
        var (groupId, _, _) = await RequireManagerForGameAsync(portfolioGameId);
        await portfolioStore.SetPortfolioPublicationAsync(portfolioGameId, groupId, isPublic);
    }

    public async Task ModerateReviewForCurrentUserAsync(
        Guid portfolioGameId,
        Guid reviewId,
        string moderationStatus)
    {
        var (groupId, platform, externalUserId) = await RequireManagerForGameAsync(portfolioGameId);

        var moderatorPlayerId = await sessionStore.ResolveEffectivePlayerIdAsync(platform, externalUserId);
        if (moderatorPlayerId is null)
        {
            throw new InvalidOperationException("Authenticated player not found.");
        }

        await portfolioStore.ModeratePortfolioReviewAsync(
            reviewId,
            portfolioGameId,
            groupId,
            moderatorPlayerId.Value,
            moderationStatus);
    }

    // --- Review submission ---

    public async Task<PortfolioReviewSubmissionState> GetReviewSubmissionStateForCurrentUserAsync(string slug)
    {
        var identity = GetCurrentIdentity();
        if (identity is null)
        {
            return PortfolioReviewSubmissionState.RequiresAuthentication;
        }

        return await portfolioStore.GetReviewSubmissionStateAsync(slug, identity.Value.Platform, identity.Value.ExternalUserId);
    }

    public async Task SubmitReviewForCurrentUserAsync(string slug, string body, bool publicationConsent)
    {
        if (!publicationConsent)
        {
            throw new InvalidOperationException("Public review requires explicit consent.");
        }

        var identity = GetCurrentIdentity();
        if (identity is null)
        {
            throw new SessionAccessDeniedException(Guid.Empty, "<anonymous>");
        }

        var normalizedSlug = PortfolioValidation.NormalizeSlug(slug);
        var normalizedBody = PortfolioValidation.NormalizeReviewBody(body);

        var displayName = identity.Value.Name?.Trim() ?? identity.Value.ExternalUserId;
        if (displayName.Length == 0)
        {
            throw new InvalidOperationException("Display name is required.");
        }

        await portfolioStore.SubmitPortfolioReviewAsync(
            normalizedSlug,
            identity.Value.Platform,
            identity.Value.ExternalUserId,
            displayName,
            normalizedBody);
    }

    // --- Internal helpers ---

    private static PortfolioGameUpdate NormalizeUpdate(PortfolioGameUpdate update)
    {
        var title = PortfolioValidation.NormalizeTitle(update.Title);
        var slug = string.IsNullOrWhiteSpace(update.PublicSlug) ? null : PortfolioValidation.NormalizeSlug(update.PublicSlug);
        var description = PortfolioValidation.NormalizeDescription(update.Description);
        var format = PortfolioValidation.NormalizeFormat(update.Format);
        var system = string.IsNullOrWhiteSpace(update.System) ? null : update.System.Trim();

        return update with
        {
            Title = title,
            PublicSlug = slug,
            Description = description,
            System = system,
            Format = format
        };
    }
}
