using Meezi.API.Models.Queue;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Meezi.Infrastructure.Services.Platform;
using Microsoft.EntityFrameworkCore;

namespace Meezi.API.Services;

public interface IQueueService
{
    Task<QueueBoardDto> GetTodayBoardAsync(string cafeId, string? branchId, CancellationToken ct = default);
    Task<(QueueTicketDto? Ticket, string? ErrorCode, string? Message)> IssuePublicAsync(
        string cafeId,
        PlanTier planTier,
        IssueQueueTicketRequest request,
        CancellationToken ct = default);
    Task<(QueueTicketDto? Ticket, string? Error)> IssueNextAsync(
        string cafeId,
        string? userId,
        IssueQueueTicketRequest request,
        CancellationToken ct = default);
    Task<(QueueTicketDto? Ticket, string? Error)> UpdateStatusAsync(
        string cafeId,
        string ticketId,
        QueueTicketStatus status,
        CancellationToken ct = default);
}

public class QueueService : IQueueService
{
    private readonly AppDbContext _db;
    private readonly IPlatformCatalogService _catalog;
    private readonly ISmsService _sms;

    public QueueService(AppDbContext db, IPlatformCatalogService catalog, ISmsService sms)
    {
        _db = db;
        _catalog = catalog;
        _sms = sms;
    }

    public async Task<(QueueTicketDto? Ticket, string? ErrorCode, string? Message)> IssuePublicAsync(
        string cafeId,
        PlanTier planTier,
        IssueQueueTicketRequest request,
        CancellationToken ct = default)
    {
        if (!await _catalog.IsFeatureEnabledForCafeAsync(cafeId, planTier, "queue", ct))
            return (null, "PLAN_LIMIT_REACHED", "Queue is not available on your plan.");

        var (ticket, error) = await IssueNextAsync(cafeId, null, request, ct);
        if (error is not null)
            return (null, error, error switch
            {
                "BRANCH_NOT_FOUND" => "Branch not found.",
                "ORDER_NOT_FOUND" => "Order not found.",
                _ => "Could not issue ticket."
            });

        return (ticket, null, null);
    }

    public async Task<QueueBoardDto> GetTodayBoardAsync(
        string cafeId,
        string? branchId,
        CancellationToken ct = default)
    {
        var today = IranCalendar.TodayInIran;
        var tickets = await FilterToday(_db.QueueTickets, cafeId, branchId, today)
            .OrderBy(q => q.Number)
            .ToListAsync(ct);

        var dtos = tickets.Select(Map).ToList();
        var waiting = tickets.Where(t => t.Status == QueueTicketStatus.Waiting).ToList();
        var nowServing = tickets
            .Where(t => t.Status == QueueTicketStatus.Called)
            .OrderByDescending(t => t.IssuedAt)
            .Select(t => (int?)t.Number)
            .FirstOrDefault();

        if (nowServing is null && waiting.Count > 0)
            nowServing = waiting[0].Number;

        var lastIssued = tickets.Count > 0 ? tickets.Max(t => t.Number) : 0;

        return new QueueBoardDto(
            today,
            nowServing,
            lastIssued,
            waiting.Count,
            dtos);
    }

    public async Task<(QueueTicketDto? Ticket, string? Error)> IssueNextAsync(
        string cafeId,
        string? userId,
        IssueQueueTicketRequest request,
        CancellationToken ct = default)
    {
        if (!string.IsNullOrEmpty(request.BranchId))
        {
            var branchOk = await _db.Branches.AnyAsync(
                b => b.Id == request.BranchId && b.CafeId == cafeId,
                ct);
            if (!branchOk) return (null, "BRANCH_NOT_FOUND");
        }

        if (!string.IsNullOrEmpty(request.OrderId))
        {
            var orderOk = await _db.Orders.AnyAsync(
                o => o.Id == request.OrderId && o.CafeId == cafeId,
                ct);
            if (!orderOk) return (null, "ORDER_NOT_FOUND");
        }

        var today = IranCalendar.TodayInIran;
        var branchId = string.IsNullOrEmpty(request.BranchId) ? null : request.BranchId;

        var maxNumber = await FilterToday(_db.QueueTickets, cafeId, branchId, today)
            .MaxAsync(q => (int?)q.Number, ct) ?? 0;

        var entity = new QueueTicket
        {
            Id = $"qt_{Guid.NewGuid():N}"[..24],
            CafeId = cafeId,
            BranchId = branchId,
            ServiceDate = today,
            Number = maxNumber + 1,
            CustomerLabel = string.IsNullOrWhiteSpace(request.CustomerLabel)
                ? null
                : request.CustomerLabel.Trim(),
            IssuedByUserId = userId,
            OrderId = request.OrderId,
            Status = QueueTicketStatus.Waiting,
            IssuedAt = DateTime.UtcNow
        };

        _db.QueueTickets.Add(entity);
        await _db.SaveChangesAsync(ct);
        return (Map(entity), null);
    }

    public async Task<(QueueTicketDto? Ticket, string? Error)> UpdateStatusAsync(
        string cafeId,
        string ticketId,
        QueueTicketStatus status,
        CancellationToken ct = default)
    {
        var ticket = await _db.QueueTickets.FirstOrDefaultAsync(
            q => q.Id == ticketId && q.CafeId == cafeId,
            ct);
        if (ticket is null) return (null, "NOT_FOUND");
        if (ticket.ServiceDate != IranCalendar.TodayInIran)
            return (null, "TICKET_EXPIRED");

        ticket.Status = status;
        await _db.SaveChangesAsync(ct);

        if (status == QueueTicketStatus.Called && !string.IsNullOrWhiteSpace(ticket.CustomerLabel))
        {
            var cafe = await _db.Cafes.AsNoTracking()
                .Where(c => c.Id == cafeId)
                .Select(c => new { c.Name })
                .FirstOrDefaultAsync(ct);
            if (cafe is not null)
            {
                var phone = ExtractPhone(ticket.CustomerLabel);
                if (phone is not null)
                {
                    try
                    {
                        await _sms.SendMessageAsync(
                            phone,
                            $"{cafe.Name}: نوبت شما فرا رسید — شماره {ticket.Number}",
                            ct);
                    }
                    catch
                    {
                        /* SMS optional */
                    }
                }
            }
        }

        return (Map(ticket), null);
    }

    private static string? ExtractPhone(string label)
    {
        var digits = new string(label.Where(char.IsDigit).ToArray());
        if (digits.Length >= 10 && digits.StartsWith("09", StringComparison.Ordinal))
            return digits.Length > 11 ? digits[^11..] : digits;
        return null;
    }

    private static IQueryable<QueueTicket> FilterToday(
        IQueryable<QueueTicket> source,
        string cafeId,
        string? branchId,
        DateOnly today)
    {
        var query = source.Where(q => q.CafeId == cafeId && q.ServiceDate == today);
        return string.IsNullOrEmpty(branchId)
            ? query
            : query.Where(q => q.BranchId == branchId);
    }

    private static QueueTicketDto Map(QueueTicket q) =>
        new(
            q.Id,
            q.BranchId,
            q.ServiceDate,
            q.Number,
            q.CustomerLabel,
            q.OrderId,
            q.Status,
            q.IssuedAt);
}
