using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Commerce;
using Tiku.Domain.Commerce;
using Tiku.Domain.Tenancy;

namespace Tiku.Infrastructure.Commerce;

internal sealed partial class CommerceAdminService
{
    public async Task<CodeBatchItem> CreateCodeBatchAsync(
        CommerceAdminActor actor,
        CreateCodeBatchCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        if (command.TotalCount is < 1 or > 1000)
            throw new CommerceException("Code batch total count must be between 1 and 1000.",
                "invalid_code_batch_count");

        if (command.Days <= 0)
            throw new CommerceException("Activation code days must be positive.", "invalid_activation_days");

        if (command.RegionId.HasValue)
        {
            var regionExists = await dbContext.Regions
                .AnyAsync(item => item.TenantId == actor.TenantId && item.Id == command.RegionId.Value,
                    cancellationToken);
            if (!regionExists) throw new CommerceException("Region was not found.", "region_not_found");
        }

        var batch = new CodeBatch
        {
            TenantId = actor.TenantId,
            RegionId = command.RegionId,
            CreatedBy = actor.UserId,
            Name = command.Name.Trim(),
            SaleType = command.SaleType?.Trim(),
            Channel = command.Channel?.Trim(),
            DefaultUnitPriceCents = command.DefaultUnitPriceCents ?? 0,
            CostPriceCents = command.CostPriceCents ?? 0,
            TotalCount = command.TotalCount,
            Days = command.Days,
            IssuedAt = DateTimeOffset.UtcNow,
            Remark = command.Remark
        };
        dbContext.CodeBatches.Add(batch);
        for (var index = 0; index < command.TotalCount; index++)
            dbContext.ActivationCodes.Add(new ActivationCode
            {
                TenantId = actor.TenantId,
                BatchId = batch.Id,
                Code = GenerateActivationCode(),
                Days = command.Days,
                SaleType = batch.SaleType,
                UnitPriceCents = batch.DefaultUnitPriceCents,
                Remark = batch.Remark
            });

        await dbContext.SaveChangesAsync(cancellationToken);
        return ToCodeBatchItem(batch);
    }

    public async Task<ActivationCodeList> GetActivationCodesAsync(
        CommerceAdminActor actor,
        CommerceAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var codes = dbContext.ActivationCodes.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId);
        if (!string.IsNullOrWhiteSpace(query.Status))
        {
            var used = string.Equals(query.Status, "used", StringComparison.OrdinalIgnoreCase);
            codes = codes.Where(item => item.IsUsed == used);
        }

        var items = await codes
            .OrderByDescending(item => item.CreatedAt)
            .Take(Math.Clamp(query.Limit ?? 50, 1, 200))
            .ToArrayAsync(cancellationToken);
        return new ActivationCodeList(items.Select(ToActivationCodeItem).ToArray());
    }

    public async Task<ActivationCodeItem> RedeemActivationCodeAsync(
        CommerceAdminActor actor,
        RedeemActivationCodeCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var code = await dbContext.ActivationCodes
                       .SingleOrDefaultAsync(item =>
                               item.TenantId == actor.TenantId &&
                               item.Code == command.Code.Trim(),
                           cancellationToken)
                   ?? throw new CommerceException("Activation code was not found.", "activation_code_not_found");
        if (code.IsUsed) throw new CommerceException("Activation code has already been used.", "activation_code_used");

        var userIsMember = await dbContext.TenantMemberships.AnyAsync(item =>
                item.TenantId == actor.TenantId &&
                item.UserId == command.UserId &&
                item.Status == MembershipStatus.Active,
            cancellationToken);
        if (!userIsMember)
            throw new CommerceException("Target user is not a tenant member.", "tenant_member_not_found");

        code.IsUsed = true;
        code.UsedBy = command.UserId;
        code.UsedRegionId = command.RegionId;
        code.UsedAt = DateTimeOffset.UtcNow;
        dbContext.Entitlements.Add(new Entitlement
        {
            TenantId = actor.TenantId,
            UserId = command.UserId,
            EntitlementType = "svip",
            SourceType = "activation_code",
            SourceId = code.Id,
            StartsAt = DateTimeOffset.UtcNow,
            ExpiresAt = DateTimeOffset.UtcNow.AddDays(code.Days),
            Status = EntitlementStatus.Active,
            Metadata = JsonSerializer.SerializeToElement(new { code.Code, code.BatchId })
        });

        await dbContext.SaveChangesAsync(cancellationToken);
        return ToActivationCodeItem(code);
    }
}