using FlatRender.IdentitySvc.Application.Services.Interfaces;
using FlatRender.IdentitySvc.Domain.Entities;
using FlatRender.IdentitySvc.Domain.Enums;
using FlatRender.IdentitySvc.Infrastructure.Data;
using FlatRender.IdentitySvc.Models.Requests;
using FlatRender.IdentitySvc.Models.Responses;
using Microsoft.EntityFrameworkCore;

namespace FlatRender.IdentitySvc.Application.Services;

public class DiscountService(IdentityDbContext db) : IDiscountService
{
    public async Task<DiscountValidateResponse> ValidateAsync(Guid tenantId, string code, Guid? planId)
    {
        var discount = await db.Discounts.FirstOrDefaultAsync(d =>
            d.TenantId == tenantId && d.Code == code && d.IsActive &&
            (d.StartsAt == null || d.StartsAt <= DateTime.UtcNow) &&
            (d.ExpiresAt == null || d.ExpiresAt >= DateTime.UtcNow) &&
            (d.MaxUseCount == null || d.UsedCount < d.MaxUseCount));

        if (discount == null)
            return new DiscountValidateResponse(false, 0, "Unknown", 0);

        if (planId.HasValue && discount.AppliesToPlanIds != null && discount.AppliesToPlanIds.Length > 0)
        {
            if (!discount.AppliesToPlanIds.Contains(planId.Value))
                return new DiscountValidateResponse(false, 0, discount.Kind.ToString(), discount.Value);
        }

        long discountMinor = 0;
        if (planId.HasValue)
        {
            var plan = await db.Plans.FindAsync(planId.Value);
            if (plan != null)
            {
                discountMinor = discount.Kind == DiscountKind.Percentage
                    ? (long)(plan.PriceMinor * (double)discount.Value / 100)
                    : (long)discount.Value;
            }
        }

        return new DiscountValidateResponse(true, discountMinor, discount.Kind.ToString(), discount.Value);
    }

    public async Task<PagedResponse<DiscountResponse>> ListAsync(Guid tenantId, int page, int pageSize)
    {
        var total = await db.Discounts.LongCountAsync(d => d.TenantId == tenantId);
        var discounts = await db.Discounts
            .Where(d => d.TenantId == tenantId)
            .OrderByDescending(d => d.CreatedAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        return new PagedResponse<DiscountResponse>(
            discounts.Select(MapResponse).ToList(),
            new PaginationMeta(page, pageSize, total, total > (long)page * pageSize)
        );
    }

    public async Task<DiscountResponse> CreateAsync(Guid tenantId, CreateDiscountRequest request)
    {
        var exists = await db.Discounts.AnyAsync(d => d.TenantId == tenantId && d.Code == request.Code);
        if (exists) throw new InvalidOperationException("Discount code already exists");

        if (!Enum.TryParse<DiscountKind>(request.Kind, true, out var kind))
            throw new ArgumentException("Invalid discount kind");

        var discount = new Discount
        {
            TenantId = tenantId,
            Name = request.Name,
            Code = request.Code.ToUpper(),
            Kind = kind,
            Value = request.Value,
            OwnerUserId = request.OwnerUserId,
            OwnerProfitPercentage = request.OwnerProfitPercentage,
            MaxUseCount = request.MaxUseCount,
            AppliesToPlanIds = request.AppliesToPlanIds,
            StartsAt = request.StartsAt,
            ExpiresAt = request.ExpiresAt,
        };
        db.Discounts.Add(discount);
        await db.SaveChangesAsync();
        return MapResponse(discount);
    }

    public async Task<DiscountResponse?> UpdateAsync(Guid tenantId, Guid id, UpdateDiscountRequest request)
    {
        var discount = await db.Discounts.FirstOrDefaultAsync(d => d.Id == id && d.TenantId == tenantId);
        if (discount == null) return null;

        if (request.Name != null) discount.Name = request.Name;
        if (request.Code != null)
        {
            var newCode = request.Code.ToUpper();
            if (newCode != discount.Code)
            {
                var clash = await db.Discounts.AnyAsync(d =>
                    d.TenantId == tenantId && d.Code == newCode && d.Id != id);
                if (clash) throw new InvalidOperationException("Discount code already exists");
                discount.Code = newCode;
            }
        }
        if (request.Kind != null)
        {
            if (!Enum.TryParse<DiscountKind>(request.Kind, true, out var kind))
                throw new ArgumentException("Invalid discount kind");
            discount.Kind = kind;
        }
        if (request.Value.HasValue) discount.Value = request.Value.Value;
        if (request.OwnerUserId.HasValue) discount.OwnerUserId = request.OwnerUserId;
        if (request.OwnerProfitPercentage.HasValue) discount.OwnerProfitPercentage = request.OwnerProfitPercentage.Value;
        if (request.MaxUseCount.HasValue) discount.MaxUseCount = request.MaxUseCount;
        if (request.AppliesToPlanIds != null) discount.AppliesToPlanIds = request.AppliesToPlanIds;
        if (request.StartsAt.HasValue) discount.StartsAt = request.StartsAt;
        if (request.ExpiresAt.HasValue) discount.ExpiresAt = request.ExpiresAt;
        if (request.IsActive.HasValue) discount.IsActive = request.IsActive.Value;
        discount.UpdatedAt = DateTime.UtcNow;

        await db.SaveChangesAsync();
        return MapResponse(discount);
    }

    public async Task<bool> DeleteAsync(Guid tenantId, Guid id)
    {
        var discount = await db.Discounts.FirstOrDefaultAsync(d => d.Id == id && d.TenantId == tenantId);
        if (discount == null) return false;
        db.Discounts.Remove(discount);
        await db.SaveChangesAsync();
        return true;
    }

    private static DiscountResponse MapResponse(Discount d) => new(
        d.Id, d.Name, d.Code, d.Kind.ToString(), d.Value,
        d.UsedCount, d.MaxUseCount, d.IsActive, d.ExpiresAt, d.CreatedAt
    );
}
