using Meezi.API.Models.Crm;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Infrastructure.Data;
using Meezi.Shared;
using Microsoft.EntityFrameworkCore;

namespace Meezi.API.Services;

public interface ICouponService
{
    Task<IReadOnlyList<CouponDto>> GetAllAsync(string cafeId, CancellationToken cancellationToken = default);
    Task<CouponDto?> GetAsync(string cafeId, string id, CancellationToken cancellationToken = default);
    Task<CouponDto?> CreateAsync(string cafeId, CreateCouponRequest request, CancellationToken cancellationToken = default);
    Task<CouponDto?> UpdateAsync(string cafeId, string id, UpdateCouponRequest request, CancellationToken cancellationToken = default);
    Task<bool> DeleteAsync(string cafeId, string id, CancellationToken cancellationToken = default);
    Task<(ValidateCouponResult? Data, ApiError? Error)> ValidateAsync(
        string cafeId,
        ValidateCouponRequest request,
        CancellationToken cancellationToken = default);
}

public class CouponService : ICouponService
{
    private readonly AppDbContext _db;

    public CouponService(AppDbContext db)
    {
        _db = db;
    }

    public async Task<IReadOnlyList<CouponDto>> GetAllAsync(string cafeId, CancellationToken cancellationToken = default)
    {
        var list = await _db.Coupons
            .Where(c => c.CafeId == cafeId)
            .OrderByDescending(c => c.CreatedAt)
            .ToListAsync(cancellationToken);
        return list.Select(ToDto).ToList();
    }

    public async Task<CouponDto?> GetAsync(string cafeId, string id, CancellationToken cancellationToken = default)
    {
        var entity = await _db.Coupons
            .FirstOrDefaultAsync(c => c.Id == id && c.CafeId == cafeId, cancellationToken);
        return entity is null ? null : ToDto(entity);
    }

    public async Task<CouponDto?> CreateAsync(
        string cafeId,
        CreateCouponRequest request,
        CancellationToken cancellationToken = default)
    {
        var codeExists = await _db.Coupons.AnyAsync(
            c => c.CafeId == cafeId && c.Code == request.Code.ToUpperInvariant(), cancellationToken);
        if (codeExists) return null;

        var entity = new Coupon
        {
            CafeId = cafeId,
            Code = request.Code.ToUpperInvariant(),
            Type = request.Type,
            Value = request.Value,
            MinOrderAmount = request.MinOrderAmount,
            MaxDiscount = request.MaxDiscount,
            UsageLimit = request.UsageLimit,
            TargetGroup = request.TargetGroup,
            StartsAt = request.StartsAt,
            ExpiresAt = request.ExpiresAt,
            IsActive = request.IsActive
        };

        _db.Coupons.Add(entity);
        await _db.SaveChangesAsync(cancellationToken);
        return ToDto(entity);
    }

    public async Task<CouponDto?> UpdateAsync(
        string cafeId,
        string id,
        UpdateCouponRequest request,
        CancellationToken cancellationToken = default)
    {
        var entity = await _db.Coupons
            .FirstOrDefaultAsync(c => c.Id == id && c.CafeId == cafeId, cancellationToken);
        if (entity is null) return null;

        if (request.Code is not null) entity.Code = request.Code.ToUpperInvariant();
        if (request.Type.HasValue) entity.Type = request.Type.Value;
        if (request.Value.HasValue) entity.Value = request.Value.Value;
        if (request.MinOrderAmount.HasValue) entity.MinOrderAmount = request.MinOrderAmount;
        if (request.MaxDiscount.HasValue) entity.MaxDiscount = request.MaxDiscount;
        if (request.UsageLimit.HasValue) entity.UsageLimit = request.UsageLimit;
        if (request.TargetGroup.HasValue) entity.TargetGroup = request.TargetGroup;
        if (request.StartsAt.HasValue) entity.StartsAt = request.StartsAt;
        if (request.ExpiresAt.HasValue) entity.ExpiresAt = request.ExpiresAt;
        if (request.IsActive.HasValue) entity.IsActive = request.IsActive.Value;

        await _db.SaveChangesAsync(cancellationToken);
        return ToDto(entity);
    }

    public async Task<bool> DeleteAsync(string cafeId, string id, CancellationToken cancellationToken = default)
    {
        var entity = await _db.Coupons
            .FirstOrDefaultAsync(c => c.Id == id && c.CafeId == cafeId, cancellationToken);
        if (entity is null) return false;

        entity.DeletedAt = DateTime.UtcNow;
        entity.IsActive = false;
        await _db.SaveChangesAsync(cancellationToken);
        return true;
    }

    public async Task<(ValidateCouponResult? Data, ApiError? Error)> ValidateAsync(
        string cafeId,
        ValidateCouponRequest request,
        CancellationToken cancellationToken = default)
    {
        var code = request.Code.Trim().ToUpperInvariant();
        if (string.IsNullOrEmpty(code))
            return (null, new ApiError("COUPON_REQUIRED", "Coupon code is required."));

        if (request.Subtotal <= 0)
            return (null, new ApiError("CART_EMPTY", "Add items before applying a coupon."));

        var coupon = await _db.Coupons.FirstOrDefaultAsync(
            c => c.CafeId == cafeId && c.Code == code,
            cancellationToken);

        if (coupon is null)
            return (null, new ApiError("COUPON_NOT_FOUND", "Coupon code is invalid."));

        if (!coupon.IsActive || coupon.DeletedAt is not null)
            return (null, new ApiError("COUPON_INACTIVE", "This coupon is not active."));

        var now = DateTime.UtcNow;
        if (coupon.StartsAt is not null && coupon.StartsAt > now)
            return (null, new ApiError("COUPON_NOT_STARTED", "This coupon is not valid yet."));

        if (coupon.ExpiresAt is not null && coupon.ExpiresAt < now)
            return (null, new ApiError("COUPON_EXPIRED", "This coupon has expired."));

        if (coupon.UsageLimit is not null && coupon.UsedCount >= coupon.UsageLimit)
            return (null, new ApiError("COUPON_LIMIT_REACHED", "This coupon has reached its usage limit."));

        if (coupon.MinOrderAmount is not null && request.Subtotal < coupon.MinOrderAmount)
            return (null, new ApiError(
                "COUPON_MIN_ORDER",
                $"Minimum order amount is {coupon.MinOrderAmount:N0} T."));

        var discount = CalculateDiscount(coupon, request.Subtotal);
        if (discount <= 0)
            return (null, new ApiError("COUPON_NO_DISCOUNT", "This coupon does not apply to this order."));

        return (new ValidateCouponResult(
            coupon.Id,
            coupon.Code,
            coupon.Type,
            coupon.Value,
            discount), null);
    }

    internal static decimal CalculateDiscount(Coupon coupon, decimal subtotal)
    {
        return coupon.Type switch
        {
            CouponType.Percentage => Math.Min(
                Math.Round(subtotal * coupon.Value / 100m, 0),
                coupon.MaxDiscount ?? subtotal),
            CouponType.FixedAmount => Math.Min(coupon.Value, subtotal),
            _ => 0m
        };
    }

    private static CouponDto ToDto(Coupon c) => new(
        c.Id,
        c.Code,
        c.Type,
        c.Value,
        c.MinOrderAmount,
        c.MaxDiscount,
        c.UsageLimit,
        c.UsedCount,
        c.TargetGroup,
        c.StartsAt,
        c.ExpiresAt,
        c.IsActive);
}
