using Microsoft.EntityFrameworkCore;
using Tiku.Application.Growth;
using Tiku.Domain.Common;
using Tiku.Domain.Growth;

namespace Tiku.Infrastructure.Growth;

public sealed partial class ReferralService
{
    public async Task<ReferralBindResult> ManualBindAsync(
        ReferralAdminActor actor,
        ManualBindReferralCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        if (command.StudentUserId == command.ReferrerUserId)
            throw new ReferralException("User cannot bind to own referral code.", "self_referral_not_allowed");

        var refCode = await dbContext.ReferralCodes
            .Where(item =>
                item.TenantId == actor.TenantId &&
                item.UserId == command.ReferrerUserId &&
                item.Status == ReferralCodeStatus.Active)
            .Select(item => item.Code)
            .FirstOrDefaultAsync(cancellationToken);
        if (refCode is null)
            refCode = (await GetOrCreateInviteCodeAsync(
                new ReferralActor(actor.TenantId, command.ReferrerUserId),
                new ReferralInviteCommand("manual"),
                cancellationToken)).InviteCode;

        var before = await dbContext.ReferralLeads
            .AsNoTracking()
            .FirstOrDefaultAsync(item =>
                    item.TenantId == actor.TenantId &&
                    item.StudentUserId == command.StudentUserId,
                cancellationToken);
        var lead = await BindLeadCoreAsync(
            actor.TenantId,
            command.StudentUserId,
            command.ReferrerUserId,
            refCode,
            NormalizeChoice(command.Source, AllowedSources, "manual", "invalid_referral_source"),
            ReferralLeadBindType.Manual,
            command.Force,
            command.Metadata,
            cancellationToken);
        lead.AssignedBy = actor.UserId;
        lead.AssignedAt = DateTimeOffset.UtcNow;
        var crmQueue = before?.ReferrerUserId == lead.ReferrerUserId
            ? null
            : await EnqueueCrmIfEnabledAsync(actor.TenantId, lead, "referral.manual_bind", cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new ReferralBindResult(ToLeadItem(lead, before?.ReferrerUserId != lead.ReferrerUserId),
            ToQueuePreview(crmQueue));
    }

    public async Task<ReferralList<ReferralTeamItem>> GetTeamAsync(
        ReferralAdminActor actor,
        ReferralTeamQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var edges = dbContext.ReferralTeamEdges
            .AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId);
        if (query.LeaderUserId.HasValue) edges = edges.Where(item => item.LeaderUserId == query.LeaderUserId.Value);

        var items = await edges
            .OrderBy(item => item.RelationType)
            .ThenBy(item => item.MemberUserId)
            .ToArrayAsync(cancellationToken);
        return new ReferralList<ReferralTeamItem>(items.Select(ToTeamItem).ToArray());
    }

    public async Task<ReferralTeamItem> UpsertTeamAsync(
        ReferralAdminActor actor,
        UpsertReferralTeamCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        await AssertActiveMemberAsync(actor.TenantId, command.MemberUserId, cancellationToken);
        if (command.LeaderUserId.HasValue)
            await AssertActiveMemberAsync(actor.TenantId, command.LeaderUserId.Value, cancellationToken);

        var relationType = ParseEnum(command.RelationType, ReferralTeamRelationType.SalesTeam,
            "invalid_referral_team_relation");
        var status = ParseEnum(command.Status, ReferralTeamEdgeStatus.Active, "invalid_referral_team_status");
        var edge = await dbContext.ReferralTeamEdges
            .FirstOrDefaultAsync(item =>
                    item.TenantId == actor.TenantId &&
                    item.MemberUserId == command.MemberUserId &&
                    item.RelationType == relationType,
                cancellationToken);
        if (edge is null)
        {
            edge = new ReferralTeamEdge
            {
                TenantId = actor.TenantId,
                MemberUserId = command.MemberUserId,
                RelationType = relationType
            };
            dbContext.ReferralTeamEdges.Add(edge);
        }

        edge.LeaderUserId = command.LeaderUserId;
        edge.Status = status;
        edge.Metadata = command.Metadata ?? JsonDefaults.Object();
        await dbContext.SaveChangesAsync(cancellationToken);
        return ToTeamItem(edge);
    }
}