using Microsoft.EntityFrameworkCore;
using Tiku.Application.Commerce;
using Tiku.Domain.Commerce;

namespace Tiku.Infrastructure.Commerce;

internal sealed partial class CommerceAdminService
{
    public async Task<TenantPointTaskList> GetPointTasksAsync(
        CommerceAdminActor actor,
        TenantPointQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var tasks = dbContext.PointActivityTasks.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId);
        if (!string.IsNullOrWhiteSpace(query.Status))
            tasks = tasks.Where(item => item.Status == ParsePointTaskStatus(query.Status));

        var items = await tasks
            .OrderBy(item => item.SortOrder)
            .ThenByDescending(item => item.CreatedAt)
            .Take(Math.Clamp(query.Limit ?? 50, 1, 200))
            .ToArrayAsync(cancellationToken);
        return new TenantPointTaskList(items);
    }

    public async Task<PointActivityTask> UpsertPointTaskAsync(
        CommerceAdminActor actor,
        UpsertPointTaskCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        if (command.Points <= 0 || command.MaxClaimsPerUser <= 0)
            throw new CommerceException("Point task points and claim limit must be positive.", "invalid_point_task");

        var task = command.Id.HasValue
            ? await dbContext.PointActivityTasks.SingleOrDefaultAsync(
                item => item.TenantId == actor.TenantId && item.Id == command.Id.Value,
                cancellationToken)
            : await dbContext.PointActivityTasks.SingleOrDefaultAsync(
                item => item.TenantId == actor.TenantId && item.TaskKey == command.TaskKey.Trim(),
                cancellationToken);
        if (task is null)
        {
            task = new PointActivityTask { TenantId = actor.TenantId };
            dbContext.PointActivityTasks.Add(task);
        }

        task.TaskKey = command.TaskKey.Trim();
        task.Title = command.Title.Trim();
        task.Description = command.Description?.Trim();
        task.TaskType = command.TaskType;
        task.Status = command.Status;
        task.Points = command.Points;
        task.MaxClaimsPerUser = command.MaxClaimsPerUser;
        task.StartsAt = command.StartsAt;
        task.EndsAt = command.EndsAt;
        task.SortOrder = command.SortOrder;
        task.Rules = JsonObjectOrDefault(command.Rules);
        task.Metadata = JsonObjectOrDefault(command.Metadata);

        await dbContext.SaveChangesAsync(cancellationToken);
        return task;
    }

    public async Task<TenantPointClaimList> GetPointClaimsAsync(
        CommerceAdminActor actor,
        TenantPointQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var claims = dbContext.PointActivityClaims.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId);
        if (query.UserId.HasValue) claims = claims.Where(item => item.UserId == query.UserId.Value);

        var items = await claims
            .OrderByDescending(item => item.CreatedAt)
            .Take(Math.Clamp(query.Limit ?? 50, 1, 200))
            .ToArrayAsync(cancellationToken);
        return new TenantPointClaimList(items);
    }

    public async Task<TenantPointExchangeItemList> GetPointExchangeItemsAsync(
        CommerceAdminActor actor,
        TenantPointQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var items = dbContext.PointExchangeItems.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId);
        if (!string.IsNullOrWhiteSpace(query.Status))
            items = items.Where(item => item.Status == ParsePointExchangeItemStatus(query.Status));

        if (query.RegionId.HasValue)
            items = items.Where(item => item.RegionId == null || item.RegionId == query.RegionId.Value);

        var result = await items
            .OrderBy(item => item.SortOrder)
            .ThenByDescending(item => item.CreatedAt)
            .Take(Math.Clamp(query.Limit ?? 50, 1, 200))
            .ToArrayAsync(cancellationToken);
        return new TenantPointExchangeItemList(result);
    }

    public async Task<PointExchangeItem> UpsertPointExchangeItemAsync(
        CommerceAdminActor actor,
        UpsertPointExchangeItemCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        if (command.PointsCost <= 0)
            throw new CommerceException("Point exchange item cost must be positive.", "invalid_point_exchange_item");

        var item = command.Id.HasValue
            ? await dbContext.PointExchangeItems.SingleOrDefaultAsync(
                entry => entry.TenantId == actor.TenantId && entry.Id == command.Id.Value,
                cancellationToken)
            : await dbContext.PointExchangeItems.SingleOrDefaultAsync(
                entry => entry.TenantId == actor.TenantId && entry.ItemKey == command.ItemKey.Trim(),
                cancellationToken);
        if (item is null)
        {
            item = new PointExchangeItem { TenantId = actor.TenantId };
            dbContext.PointExchangeItems.Add(item);
        }

        item.RegionId = command.RegionId;
        item.ItemKey = command.ItemKey.Trim();
        item.Name = command.Name.Trim();
        item.Description = command.Description?.Trim();
        item.ItemType = command.ItemType;
        item.Status = command.Status;
        item.PointsCost = command.PointsCost;
        item.Stock = command.Stock;
        item.Days = command.Days;
        item.SortOrder = command.SortOrder;
        item.StartsAt = command.StartsAt;
        item.EndsAt = command.EndsAt;
        item.FulfillmentPayload = JsonObjectOrDefault(command.FulfillmentPayload);
        item.Metadata = JsonObjectOrDefault(command.Metadata);

        await dbContext.SaveChangesAsync(cancellationToken);
        return item;
    }

    public async Task<TenantPointExchangeOrderList> GetPointExchangeOrdersAsync(
        CommerceAdminActor actor,
        TenantPointQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var orders = dbContext.PointExchangeOrders.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId);
        if (query.UserId.HasValue) orders = orders.Where(item => item.UserId == query.UserId.Value);

        if (!string.IsNullOrWhiteSpace(query.Status))
            orders = orders.Where(item => item.Status == ParsePointExchangeOrderStatus(query.Status));

        var result = await orders
            .OrderByDescending(item => item.CreatedAt)
            .Take(Math.Clamp(query.Limit ?? 50, 1, 200))
            .ToArrayAsync(cancellationToken);
        return new TenantPointExchangeOrderList(result);
    }

    public async Task<PointExchangeOrder> UpdatePointExchangeOrderStatusAsync(
        CommerceAdminActor actor,
        UpdatePointExchangeOrderStatusCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var order = await dbContext.PointExchangeOrders
                        .SingleOrDefaultAsync(item => item.TenantId == actor.TenantId && item.Id == command.OrderId,
                            cancellationToken)
                    ?? throw new CommerceException("Point exchange order was not found.",
                        "point_exchange_order_not_found");
        order.Status = command.Status;
        if (command.Status == PointExchangeOrderStatus.Completed)
        {
            order.CompletedAt ??= DateTimeOffset.UtcNow;
            order.CancelledAt = null;
        }
        else if (command.Status == PointExchangeOrderStatus.Cancelled)
        {
            order.CancelledAt ??= DateTimeOffset.UtcNow;
        }

        await dbContext.SaveChangesAsync(cancellationToken);
        return order;
    }
}