using System.Text.Json;

namespace Tiku.Application.Growth;

public sealed record ReferralActor(Guid TenantId, Guid? UserId = null);

public sealed record ReferralAdminActor(Guid TenantId, Guid UserId);

public sealed record ReferralInviteCommand(string? Channel = null, string? LandingPath = null);

public sealed record ResolveReferralCommand(string? Code);

public sealed record TrackReferralEventCommand(
    string RefCode,
    string? EventType,
    string? Source,
    Guid? TargetUserId,
    JsonElement? Metadata);

public sealed record BindReferralCommand(
    string RefCode,
    string? Source,
    JsonElement? Metadata);

public sealed record ReferralQrcodeCommand(
    string? Page,
    string? Scene,
    string? Provider,
    string? QrcodeUrl,
    JsonElement? Metadata);

public sealed record ReferralQrcodeGenerateRequest(
    Guid TenantId,
    Guid UserId,
    string RefCode,
    string Provider,
    string Page,
    string Scene);

public sealed record ReferralQrcodeGenerateResult(
    string QrcodeUrl,
    string Provider,
    JsonElement Metadata);

public sealed record ReferralStatsQuery(Guid? ReferrerUserId = null, int? Limit = null);

public sealed record ReferralConversionQuery(
    Guid? ReferrerUserId = null,
    DateOnly? StartDate = null,
    DateOnly? EndDate = null,
    int? Days = null,
    int? Limit = null);

public sealed record ManualBindReferralCommand(
    Guid StudentUserId,
    Guid ReferrerUserId,
    string? Source,
    bool Force,
    JsonElement? Metadata);

public sealed record ReferralTeamQuery(Guid? LeaderUserId = null);

public sealed record UpsertReferralTeamCommand(
    Guid MemberUserId,
    Guid? LeaderUserId,
    string? RelationType,
    string? Status,
    JsonElement? Metadata);

public sealed record ReferralInviteItem(string InviteCode);

public sealed record ReferralResolutionItem(
    bool Valid,
    Guid? InviterId,
    string? InviteCode,
    string? Role,
    string? Name);

public sealed record ReferralTrackItem(
    Guid Id,
    string EventType,
    string? RefCode,
    Guid? ReferrerUserId,
    Guid? TargetUserId,
    string? Source,
    DateTimeOffset CreatedAt);

public sealed record ReferralLeadItem(
    Guid Id,
    Guid StudentUserId,
    Guid? ReferrerUserId,
    string? RefCode,
    string Status,
    DateTimeOffset BoundAt,
    bool Changed);

public sealed record ReferralQrcodeItem(
    Guid Id,
    string RefCode,
    string Scene,
    string Page,
    string Provider,
    string? QrcodeUrl,
    string Status,
    JsonElement Metadata);

public sealed record ReferralTrackResult(ReferralTrackItem Item, ReferralLeadItem? Lead, CrmQueuePreviewItem? CrmQueue);

public sealed record ReferralBindResult(ReferralLeadItem Lead, CrmQueuePreviewItem? CrmQueue);

public sealed record CrmQueuePreviewItem(Guid Id, string Status, string? Source);

public sealed record ReferralStatsItem(
    Guid ReferrerUserId,
    string? Name,
    string Role,
    string? InviteCode,
    int LeadCount,
    int PaidLeadCount,
    int PaidAmountCents,
    int TrackCount,
    decimal ConversionRate);

public sealed record ReferralList<T>(IReadOnlyCollection<T> Items);

public sealed record ReferralConversionReport(
    DateOnly StartDate,
    DateOnly EndDate,
    int LeadCount,
    int PaidLeadCount,
    int PaidAmountCents,
    decimal ConversionRate,
    IReadOnlyCollection<ReferralStatsItem> TopReferrers,
    IReadOnlyCollection<ReferralLeadItem> RecentUnconvertedLeads);

public sealed record ReferralTeamItem(
    Guid Id,
    Guid MemberUserId,
    Guid? LeaderUserId,
    string RelationType,
    string Status,
    JsonElement Metadata);

public interface IReferralService
{
    Task<ReferralInviteItem> GetOrCreateInviteCodeAsync(
        ReferralActor actor,
        ReferralInviteCommand command,
        CancellationToken cancellationToken = default);

    Task<ReferralResolutionItem> ResolveAsync(
        ReferralActor actor,
        ResolveReferralCommand command,
        CancellationToken cancellationToken = default);

    Task<ReferralTrackResult> TrackEventAsync(
        ReferralActor actor,
        TrackReferralEventCommand command,
        string? ipAddress,
        string? userAgent,
        CancellationToken cancellationToken = default);

    Task<ReferralBindResult> BindAsync(
        ReferralActor actor,
        BindReferralCommand command,
        CancellationToken cancellationToken = default);

    Task<ReferralQrcodeItem> GetOrCreateQrcodeAsync(
        ReferralActor actor,
        ReferralQrcodeCommand command,
        CancellationToken cancellationToken = default);

    Task<ReferralStatsItem> GetStatsAsync(
        ReferralAdminActor actor,
        ReferralStatsQuery query,
        CancellationToken cancellationToken = default);

    Task<ReferralList<ReferralStatsItem>> GetSalesStatsAsync(
        ReferralAdminActor actor,
        ReferralStatsQuery query,
        CancellationToken cancellationToken = default);

    Task<ReferralConversionReport> GetConversionReportAsync(
        ReferralAdminActor actor,
        ReferralConversionQuery query,
        CancellationToken cancellationToken = default);

    Task<ReferralList<ReferralLeadItem>> GetClientsAsync(
        ReferralAdminActor actor,
        ReferralStatsQuery query,
        CancellationToken cancellationToken = default);

    Task<ReferralBindResult> ManualBindAsync(
        ReferralAdminActor actor,
        ManualBindReferralCommand command,
        CancellationToken cancellationToken = default);

    Task<ReferralList<ReferralTeamItem>> GetTeamAsync(
        ReferralAdminActor actor,
        ReferralTeamQuery query,
        CancellationToken cancellationToken = default);

    Task<ReferralTeamItem> UpsertTeamAsync(
        ReferralAdminActor actor,
        UpsertReferralTeamCommand command,
        CancellationToken cancellationToken = default);
}

public interface IReferralQrcodeGenerator
{
    Task<ReferralQrcodeGenerateResult> GenerateAsync(
        ReferralQrcodeGenerateRequest request,
        CancellationToken cancellationToken = default);
}

public sealed class ReferralException(string message, string code) : Exception(message)
{
    public string Code { get; } = code;
}