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

namespace Meezi.Infrastructure.Services;

public interface ISupportTicketService
{
    Task<IReadOnlyList<SupportTicketDto>> ListForCafeAsync(string cafeId, CancellationToken cancellationToken = default);
    Task<SupportTicketDetailDto?> GetForCafeAsync(string cafeId, string ticketId, CancellationToken cancellationToken = default);
    Task<SupportTicketDetailDto?> CreateForCafeAsync(
        string cafeId,
        string employeeId,
        CreateSupportTicketRequest request,
        CancellationToken cancellationToken = default);
    Task<SupportTicketDetailDto?> ReplyAsMerchantAsync(
        string cafeId,
        string ticketId,
        string employeeId,
        ReplySupportTicketRequest request,
        CancellationToken cancellationToken = default);

    Task<IReadOnlyList<SupportTicketDto>> ListAllAsync(
        SupportTicketStatus? status,
        CancellationToken cancellationToken = default);
    Task<SupportTicketDetailDto?> GetAdminAsync(string ticketId, CancellationToken cancellationToken = default);
    Task<SupportTicketDetailDto?> ReplyAsAdminAsync(
        string ticketId,
        string adminId,
        ReplySupportTicketRequest request,
        CancellationToken cancellationToken = default);
    Task<SupportTicketDetailDto?> UpdateAdminAsync(
        string ticketId,
        UpdateSupportTicketRequest request,
        CancellationToken cancellationToken = default);
}

public class SupportTicketService : ISupportTicketService
{
    private readonly AppDbContext _db;

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

    public async Task<IReadOnlyList<SupportTicketDto>> ListForCafeAsync(
        string cafeId,
        CancellationToken cancellationToken = default)
    {
        return await QueryTickets()
            .Where(t => t.CafeId == cafeId)
            .OrderByDescending(t => t.UpdatedAt)
            .ToListAsync(cancellationToken);
    }

    public async Task<SupportTicketDetailDto?> GetForCafeAsync(
        string cafeId,
        string ticketId,
        CancellationToken cancellationToken = default)
    {
        var ticket = await _db.SupportTickets
            .AsNoTracking()
            .Include(t => t.Cafe)
            .Include(t => t.CreatedByEmployee)
            .FirstOrDefaultAsync(t => t.Id == ticketId && t.CafeId == cafeId, cancellationToken);
        if (ticket is null) return null;

        var messages = await LoadMessagesAsync(ticketId, cancellationToken);
        return new SupportTicketDetailDto(MapTicket(ticket), messages);
    }

    public async Task<SupportTicketDetailDto?> CreateForCafeAsync(
        string cafeId,
        string employeeId,
        CreateSupportTicketRequest request,
        CancellationToken cancellationToken = default)
    {
        var ticket = new SupportTicket
        {
            CafeId = cafeId,
            Subject = request.Subject.Trim(),
            Priority = request.Priority ?? SupportTicketPriority.Normal,
            CreatedByEmployeeId = employeeId,
            Status = SupportTicketStatus.Open
        };
        _db.SupportTickets.Add(ticket);
        await _db.SaveChangesAsync(cancellationToken);

        await AddMessageAsync(ticket.Id, TicketMessageSenderKind.Merchant, employeeId, request.Body, cancellationToken);
        ticket.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(cancellationToken);
        return await GetForCafeAsync(cafeId, ticket.Id, cancellationToken);
    }

    public async Task<SupportTicketDetailDto?> ReplyAsMerchantAsync(
        string cafeId,
        string ticketId,
        string employeeId,
        ReplySupportTicketRequest request,
        CancellationToken cancellationToken = default)
    {
        var ticket = await _db.SupportTickets
            .FirstOrDefaultAsync(t => t.Id == ticketId && t.CafeId == cafeId, cancellationToken);
        if (ticket is null) return null;
        if (ticket.Status is SupportTicketStatus.Closed or SupportTicketStatus.Resolved)
            return null;

        await AddMessageAsync(ticketId, TicketMessageSenderKind.Merchant, employeeId, request.Body, cancellationToken);
        ticket.Status = SupportTicketStatus.Open;
        ticket.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(cancellationToken);
        return await GetForCafeAsync(cafeId, ticketId, cancellationToken);
    }

    public async Task<IReadOnlyList<SupportTicketDto>> ListAllAsync(
        SupportTicketStatus? status,
        CancellationToken cancellationToken = default)
    {
        var q = QueryTickets();
        if (status.HasValue)
            q = q.Where(t => t.Status == status.Value);

        return await q.OrderByDescending(t => t.UpdatedAt).ToListAsync(cancellationToken);
    }

    public async Task<SupportTicketDetailDto?> GetAdminAsync(
        string ticketId,
        CancellationToken cancellationToken = default)
    {
        var ticket = await _db.SupportTickets
            .AsNoTracking()
            .Include(t => t.Cafe)
            .Include(t => t.CreatedByEmployee)
            .FirstOrDefaultAsync(t => t.Id == ticketId, cancellationToken);
        if (ticket is null) return null;

        var messages = await LoadMessagesAsync(ticketId, cancellationToken);
        return new SupportTicketDetailDto(MapTicket(ticket), messages);
    }

    public async Task<SupportTicketDetailDto?> ReplyAsAdminAsync(
        string ticketId,
        string adminId,
        ReplySupportTicketRequest request,
        CancellationToken cancellationToken = default)
    {
        var ticket = await _db.SupportTickets.FirstOrDefaultAsync(t => t.Id == ticketId, cancellationToken);
        if (ticket is null) return null;
        if (ticket.Status is SupportTicketStatus.Closed)
            return null;

        await AddMessageAsync(ticketId, TicketMessageSenderKind.Admin, adminId, request.Body, cancellationToken);
        ticket.Status = SupportTicketStatus.WaitingMerchant;
        ticket.AssignedAdminId ??= adminId;
        ticket.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(cancellationToken);
        return await GetAdminAsync(ticketId, cancellationToken);
    }

    public async Task<SupportTicketDetailDto?> UpdateAdminAsync(
        string ticketId,
        UpdateSupportTicketRequest request,
        CancellationToken cancellationToken = default)
    {
        var ticket = await _db.SupportTickets.FirstOrDefaultAsync(t => t.Id == ticketId, cancellationToken);
        if (ticket is null) return null;

        if (request.Status.HasValue)
        {
            ticket.Status = request.Status.Value;
            if (request.Status is SupportTicketStatus.Closed or SupportTicketStatus.Resolved)
                ticket.ClosedAt = DateTime.UtcNow;
        }

        if (request.Priority.HasValue)
            ticket.Priority = request.Priority.Value;
        if (request.AssignedAdminId is not null)
            ticket.AssignedAdminId = request.AssignedAdminId;

        ticket.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(cancellationToken);
        return await GetAdminAsync(ticketId, cancellationToken);
    }

    private IQueryable<SupportTicketDto> QueryTickets() =>
        _db.SupportTickets
            .AsNoTracking()
            .Select(t => new SupportTicketDto(
                t.Id,
                t.CafeId,
                t.Cafe != null ? t.Cafe.Name : "",
                t.Subject,
                t.Status,
                t.Priority,
                t.CreatedByEmployeeId,
                t.CreatedByEmployee != null ? t.CreatedByEmployee.Name : null,
                t.AssignedAdminId,
                t.CreatedAt,
                t.UpdatedAt,
                t.Messages.Count));

    private static SupportTicketDto MapTicket(SupportTicket t) =>
        new(
            t.Id,
            t.CafeId,
            t.Cafe?.Name ?? "",
            t.Subject,
            t.Status,
            t.Priority,
            t.CreatedByEmployeeId,
            t.CreatedByEmployee?.Name,
            t.AssignedAdminId,
            t.CreatedAt,
            t.UpdatedAt,
            t.Messages.Count);

    private async Task<IReadOnlyList<SupportTicketMessageDto>> LoadMessagesAsync(
        string ticketId,
        CancellationToken cancellationToken)
    {
        var messages = await _db.SupportTicketMessages
            .AsNoTracking()
            .Where(m => m.TicketId == ticketId)
            .OrderBy(m => m.CreatedAt)
            .ToListAsync(cancellationToken);

        var employeeIds = messages
            .Where(m => m.SenderKind == TicketMessageSenderKind.Merchant)
            .Select(m => m.SenderId)
            .Distinct()
            .ToList();
        var adminIds = messages
            .Where(m => m.SenderKind == TicketMessageSenderKind.Admin)
            .Select(m => m.SenderId)
            .Distinct()
            .ToList();

        var employeeNames = await _db.Employees
            .AsNoTracking()
            .Where(e => employeeIds.Contains(e.Id))
            .ToDictionaryAsync(e => e.Id, e => e.Name, cancellationToken);
        var adminNames = await _db.SystemAdmins
            .AsNoTracking()
            .Where(a => adminIds.Contains(a.Id))
            .ToDictionaryAsync(a => a.Id, a => a.Name, cancellationToken);

        return messages.Select(m => new SupportTicketMessageDto(
            m.Id,
            m.SenderKind,
            m.SenderId,
            m.SenderKind == TicketMessageSenderKind.Admin
                ? adminNames.GetValueOrDefault(m.SenderId)
                : employeeNames.GetValueOrDefault(m.SenderId),
            m.Body,
            m.CreatedAt)).ToList();
    }

    private async Task AddMessageAsync(
        string ticketId,
        TicketMessageSenderKind kind,
        string senderId,
        string body,
        CancellationToken cancellationToken)
    {
        _db.SupportTicketMessages.Add(new SupportTicketMessage
        {
            TicketId = ticketId,
            SenderKind = kind,
            SenderId = senderId,
            Body = body.Trim()
        });
        await _db.SaveChangesAsync(cancellationToken);
    }
}
