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

namespace Meezi.API.Services;

public interface IBranchLifecycleService
{
    Task<(bool Ok, string? ErrorCode, string? Message)> ScheduleDeletionAsync(
        string cafeId,
        string branchId,
        CancellationToken cancellationToken = default);

    Task<(bool Ok, string? ErrorCode, string? Message)> RestoreAsync(
        string cafeId,
        string branchId,
        CancellationToken cancellationToken = default);

    Task<int> PurgeExpiredDeletionsAsync(CancellationToken cancellationToken = default);
}

public class BranchLifecycleService : IBranchLifecycleService
{
    public const int RecoveryDays = 7;

    private readonly AppDbContext _db;
    private readonly ILogger<BranchLifecycleService> _logger;

    public BranchLifecycleService(AppDbContext db, ILogger<BranchLifecycleService> logger)
    {
        _db = db;
        _logger = logger;
    }

    public async Task<(bool Ok, string? ErrorCode, string? Message)> ScheduleDeletionAsync(
        string cafeId,
        string branchId,
        CancellationToken cancellationToken = default)
    {
        var branch = await _db.Branches
            .FirstOrDefaultAsync(b => b.Id == branchId && b.CafeId == cafeId, cancellationToken);
        if (branch is null)
            return (false, "NOT_FOUND", "Branch not found.");
        if (branch.DeletedAt is not null)
            return (false, "ALREADY_DELETED", "Branch is already scheduled for removal.");

        var activeCount = await _db.Branches.CountAsync(
            b => b.CafeId == cafeId && b.DeletedAt == null,
            cancellationToken);
        if (activeCount <= 1)
            return (false, "LAST_BRANCH", "At least one active branch is required.");

        var now = DateTime.UtcNow;
        var purgeAt = now.AddDays(RecoveryDays);
        branch.DeletedAt = now;
        branch.ScheduledPermanentDeleteAt = purgeAt;
        branch.IsActive = false;
        branch.UpdatedAt = now;

        await SoftDeleteBranchScopedDataAsync(cafeId, branchId, now, cancellationToken);
        await _db.SaveChangesAsync(cancellationToken);
        return (true, null, null);
    }

    public async Task<(bool Ok, string? ErrorCode, string? Message)> RestoreAsync(
        string cafeId,
        string branchId,
        CancellationToken cancellationToken = default)
    {
        var branch = await _db.Branches
            .IgnoreQueryFilters()
            .FirstOrDefaultAsync(b => b.Id == branchId && b.CafeId == cafeId, cancellationToken);
        if (branch is null || branch.DeletedAt is null)
            return (false, "NOT_FOUND", "Branch is not pending deletion.");
        if (branch.ScheduledPermanentDeleteAt is not null
            && branch.ScheduledPermanentDeleteAt <= DateTime.UtcNow)
            return (false, "PURGE_EXPIRED", "Recovery period has ended.");

        var deletedAt = branch.DeletedAt.Value;
        branch.DeletedAt = null;
        branch.ScheduledPermanentDeleteAt = null;
        branch.IsActive = true;
        branch.UpdatedAt = DateTime.UtcNow;

        await RestoreBranchScopedDataAsync(cafeId, branchId, deletedAt, cancellationToken);
        await _db.SaveChangesAsync(cancellationToken);
        return (true, null, null);
    }

    public async Task<int> PurgeExpiredDeletionsAsync(CancellationToken cancellationToken = default)
    {
        var now = DateTime.UtcNow;
        var expired = await _db.Branches
            .IgnoreQueryFilters()
            .Where(b => b.DeletedAt != null && b.ScheduledPermanentDeleteAt != null && b.ScheduledPermanentDeleteAt <= now)
            .Select(b => new { b.Id, b.CafeId })
            .ToListAsync(cancellationToken);

        var purged = 0;
        foreach (var b in expired)
        {
            try
            {
                await HardDeleteBranchAsync(b.CafeId, b.Id, cancellationToken);
                purged++;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to permanently delete branch {BranchId}", b.Id);
            }
        }

        return purged;
    }

    private async Task SoftDeleteBranchScopedDataAsync(
        string cafeId,
        string branchId,
        DateTime deletedAt,
        CancellationToken cancellationToken)
    {
        await _db.Tables
            .Where(t => t.CafeId == cafeId && t.BranchId == branchId && t.DeletedAt == null)
            .ExecuteUpdateAsync(s => s
                .SetProperty(t => t.DeletedAt, deletedAt)
                .SetProperty(t => t.IsActive, false),
                cancellationToken);

        await _db.TableSections
            .Where(s => s.CafeId == cafeId && s.BranchId == branchId && s.DeletedAt == null)
            .ExecuteUpdateAsync(s => s.SetProperty(x => x.DeletedAt, deletedAt), cancellationToken);

        await _db.Employees
            .Where(e => e.CafeId == cafeId && e.BranchId == branchId && e.DeletedAt == null)
            .ExecuteUpdateAsync(s => s.SetProperty(e => e.DeletedAt, deletedAt), cancellationToken);

        await _db.BranchMenuItemOverrides
            .Where(o => o.CafeId == cafeId && o.BranchId == branchId && o.DeletedAt == null)
            .ExecuteUpdateAsync(s => s.SetProperty(o => o.DeletedAt, deletedAt), cancellationToken);

        await _db.RegisterShifts
            .Where(s => s.CafeId == cafeId && s.BranchId == branchId && s.DeletedAt == null)
            .ExecuteUpdateAsync(s => s.SetProperty(s => s.DeletedAt, deletedAt), cancellationToken);

        await _db.Expenses
            .Where(e => e.CafeId == cafeId && e.BranchId == branchId && e.DeletedAt == null)
            .ExecuteUpdateAsync(s => s.SetProperty(e => e.DeletedAt, deletedAt), cancellationToken);

        await _db.DailyReports
            .Where(r => r.CafeId == cafeId && r.BranchId == branchId && r.DeletedAt == null)
            .ExecuteUpdateAsync(s => s.SetProperty(r => r.DeletedAt, deletedAt), cancellationToken);
    }

    private async Task RestoreBranchScopedDataAsync(
        string cafeId,
        string branchId,
        DateTime deletedAt,
        CancellationToken cancellationToken)
    {
        await _db.Tables
            .IgnoreQueryFilters()
            .Where(t => t.CafeId == cafeId && t.BranchId == branchId && t.DeletedAt == deletedAt)
            .ExecuteUpdateAsync(s => s
                .SetProperty(t => t.DeletedAt, (DateTime?)null)
                .SetProperty(t => t.IsActive, true),
                cancellationToken);

        await _db.TableSections
            .IgnoreQueryFilters()
            .Where(s => s.CafeId == cafeId && s.BranchId == branchId && s.DeletedAt == deletedAt)
            .ExecuteUpdateAsync(s => s.SetProperty(x => x.DeletedAt, (DateTime?)null), cancellationToken);

        await _db.Employees
            .IgnoreQueryFilters()
            .Where(e => e.CafeId == cafeId && e.BranchId == branchId && e.DeletedAt == deletedAt)
            .ExecuteUpdateAsync(s => s.SetProperty(e => e.DeletedAt, (DateTime?)null), cancellationToken);

        await _db.BranchMenuItemOverrides
            .IgnoreQueryFilters()
            .Where(o => o.CafeId == cafeId && o.BranchId == branchId && o.DeletedAt == deletedAt)
            .ExecuteUpdateAsync(s => s.SetProperty(o => o.DeletedAt, (DateTime?)null), cancellationToken);

        await _db.RegisterShifts
            .IgnoreQueryFilters()
            .Where(s => s.CafeId == cafeId && s.BranchId == branchId && s.DeletedAt == deletedAt)
            .ExecuteUpdateAsync(s => s.SetProperty(s => s.DeletedAt, (DateTime?)null), cancellationToken);

        await _db.Expenses
            .IgnoreQueryFilters()
            .Where(e => e.CafeId == cafeId && e.BranchId == branchId && e.DeletedAt == deletedAt)
            .ExecuteUpdateAsync(s => s.SetProperty(e => e.DeletedAt, (DateTime?)null), cancellationToken);

        await _db.DailyReports
            .IgnoreQueryFilters()
            .Where(r => r.CafeId == cafeId && r.BranchId == branchId && r.DeletedAt == deletedAt)
            .ExecuteUpdateAsync(s => s.SetProperty(r => r.DeletedAt, (DateTime?)null), cancellationToken);
    }

    private async Task HardDeleteBranchAsync(
        string cafeId,
        string branchId,
        CancellationToken cancellationToken)
    {
        await _db.BranchMenuItemOverrides
            .IgnoreQueryFilters()
            .Where(o => o.CafeId == cafeId && o.BranchId == branchId)
            .ExecuteDeleteAsync(cancellationToken);

        await _db.Tables
            .IgnoreQueryFilters()
            .Where(t => t.CafeId == cafeId && t.BranchId == branchId)
            .ExecuteDeleteAsync(cancellationToken);

        await _db.TableSections
            .IgnoreQueryFilters()
            .Where(s => s.CafeId == cafeId && s.BranchId == branchId)
            .ExecuteDeleteAsync(cancellationToken);

        await _db.RegisterShifts
            .IgnoreQueryFilters()
            .Where(s => s.CafeId == cafeId && s.BranchId == branchId)
            .ExecuteDeleteAsync(cancellationToken);

        await _db.Expenses
            .IgnoreQueryFilters()
            .Where(e => e.CafeId == cafeId && e.BranchId == branchId)
            .ExecuteDeleteAsync(cancellationToken);

        await _db.DailyReports
            .IgnoreQueryFilters()
            .Where(r => r.CafeId == cafeId && r.BranchId == branchId)
            .ExecuteDeleteAsync(cancellationToken);

        await _db.Employees
            .IgnoreQueryFilters()
            .Where(e => e.CafeId == cafeId && e.BranchId == branchId)
            .ExecuteDeleteAsync(cancellationToken);

        await _db.Branches
            .IgnoreQueryFilters()
            .Where(b => b.Id == branchId && b.CafeId == cafeId)
            .ExecuteDeleteAsync(cancellationToken);

        _logger.LogInformation("Permanently deleted branch {BranchId} for cafe {CafeId}", branchId, cafeId);
    }
}
