using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Meezi.API.Models.Branches;
using Meezi.API.Services;
using Meezi.Core.Constants;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Core.Utilities;
using Meezi.Infrastructure.Data;
using Meezi.Shared;

namespace Meezi.API.Controllers;

[Route("api/cafes/{cafeId}/branches")]
public class BranchesController : CafeApiControllerBase
{
    private const string PlanLimitMessage =
        "Branch limit reached for your plan. Upgrade to Pro or Business to add more branches.";

    private readonly AppDbContext _db;
    private readonly IBranchLifecycleService _lifecycle;
    private readonly IValidator<CreateBranchRequest> _createValidator;
    private readonly IValidator<PatchBranchRequest> _patchValidator;

    public BranchesController(
        AppDbContext db,
        IBranchLifecycleService lifecycle,
        IValidator<CreateBranchRequest> createValidator,
        IValidator<PatchBranchRequest> patchValidator)
    {
        _db = db;
        _lifecycle = lifecycle;
        _createValidator = createValidator;
        _patchValidator = patchValidator;
    }

    [HttpGet]
    public async Task<IActionResult> List(
        string cafeId,
        ITenantContext tenant,
        [FromQuery] bool activeOnly = false,
        [FromQuery] bool includePendingDeletion = false,
        CancellationToken ct = default)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;

        var now = DateTime.UtcNow;
        List<Branch> branches;

        if (includePendingDeletion)
        {
            branches = await _db.Branches
                .IgnoreQueryFilters()
                .Where(b => b.CafeId == cafeId
                    && (b.DeletedAt == null
                        || (b.ScheduledPermanentDeleteAt != null && b.ScheduledPermanentDeleteAt > now)))
                .OrderBy(b => b.DeletedAt == null ? 0 : 1)
                .ThenBy(b => b.Name)
                .ToListAsync(ct);
        }
        else
        {
            var query = _db.Branches.Where(b => b.CafeId == cafeId);
            if (activeOnly)
                query = query.Where(b => b.IsActive);
            branches = await query.OrderBy(b => b.Name).ToListAsync(ct);
        }

        var branchIds = branches.Select(b => b.Id).ToList();
        var managers = await _db.Employees
            .AsNoTracking()
            .IgnoreQueryFilters()
            .Where(e => e.CafeId == cafeId && e.BranchId != null && branchIds.Contains(e.BranchId!)
                && e.Role == EmployeeRole.Manager && e.DeletedAt == null)
            .ToListAsync(ct);
        var managerByBranch = managers
            .GroupBy(e => e.BranchId!)
            .ToDictionary(g => g.Key, g => g.First());

        var data = branches.Select(b =>
        {
            managerByBranch.TryGetValue(b.Id, out var mgr);
            return ToDto(b, mgr?.Phone, mgr?.Name, now);
        }).ToList();

        return Ok(new ApiResponse<IReadOnlyList<BranchDto>>(true, data));
    }

    [HttpPost]
    public async Task<IActionResult> Create(
        string cafeId,
        [FromBody] CreateBranchRequest request,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsureOwner(tenant) is { } ownerDenied) return ownerDenied;

        var validation = await _createValidator.ValidateAsync(request, ct);
        if (!validation.IsValid)
        {
            var first = validation.Errors.First();
            return BadRequest(new ApiResponse<object>(false, null,
                new ApiError("VALIDATION_ERROR", first.ErrorMessage, first.PropertyName)));
        }

        var loginPhone = PhoneNormalizer.Normalize(request.LoginPhone);
        var phoneTaken = await _db.Employees.AnyAsync(
            e => e.CafeId == cafeId && e.Phone == loginPhone && e.DeletedAt == null, ct);
        if (phoneTaken)
        {
            return BadRequest(new ApiResponse<object>(false, null,
                new ApiError("PHONE_ALREADY_REGISTERED",
                    "This mobile number is already used for login at this cafe.")));
        }

        var tier = tenant.PlanTier ?? PlanTier.Free;
        var count = await _db.Branches.CountAsync(b => b.CafeId == cafeId, ct);
        var max = PlanLimits.MaxBranches(tier);
        if (count >= max)
            return StatusCode(403, new ApiResponse<object>(false, null,
                new ApiError("PLAN_LIMIT_REACHED", PlanLimitMessage)));

        var now = DateTime.UtcNow;
        var branchId = $"branch_{Guid.NewGuid():N}"[..24];
        var managerName = string.IsNullOrWhiteSpace(request.ManagerName)
            ? request.Name.Trim()
            : request.ManagerName.Trim();

        var branch = new Branch
        {
            Id = branchId,
            CafeId = cafeId,
            Name = request.Name.Trim(),
            Address = request.Address?.Trim(),
            City = request.City?.Trim(),
            Phone = request.Phone?.Trim(),
            IsActive = true,
            CreatedAt = now,
            UpdatedAt = now
        };

        var employee = new Employee
        {
            Id = $"emp_{Guid.NewGuid():N}"[..24],
            CafeId = cafeId,
            BranchId = branchId,
            Name = managerName,
            Phone = loginPhone,
            Role = EmployeeRole.Manager,
            CreatedAt = now
        };

        _db.Branches.Add(branch);
        _db.Employees.Add(employee);
        await _db.SaveChangesAsync(ct);

        return Ok(new ApiResponse<BranchDto>(true, ToDto(branch, loginPhone, managerName, now)));
    }

    [HttpPatch("{branchId}")]
    public async Task<IActionResult> Patch(
        string cafeId,
        string branchId,
        [FromBody] PatchBranchRequest request,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsureOwner(tenant) is { } ownerDenied) return ownerDenied;

        var validation = await _patchValidator.ValidateAsync(request, ct);
        if (!validation.IsValid)
        {
            var first = validation.Errors.First();
            return BadRequest(new ApiResponse<object>(false, null,
                new ApiError("VALIDATION_ERROR", first.ErrorMessage, first.PropertyName)));
        }

        var branch = await _db.Branches.FirstOrDefaultAsync(b => b.Id == branchId && b.CafeId == cafeId, ct);
        if (branch is null) return NotFoundError();

        if (request.TaxRate.HasValue)
        {
            var cafe = await _db.Cafes.AsNoTracking().FirstAsync(c => c.Id == cafeId, ct);
            if (!cafe.AllowBranchTaxOverride)
            {
                return BadRequest(new ApiResponse<object>(false, null,
                    new ApiError("TAX_OVERRIDE_NOT_ALLOWED",
                        "تغییر نرخ مالیات شعبه توسط مالک غیرفعال شده است")));
            }
        }

        if (request.Name is not null) branch.Name = request.Name.Trim();
        if (request.Address is not null) branch.Address = request.Address.Trim();
        if (request.City is not null) branch.City = request.City.Trim();
        if (request.Phone is not null) branch.Phone = request.Phone.Trim();
        if (request.IsActive.HasValue) branch.IsActive = request.IsActive.Value;
        if (request.LogoUrl is not null) branch.LogoUrl = string.IsNullOrWhiteSpace(request.LogoUrl) ? null : request.LogoUrl.Trim();
        if (request.WelcomeText is not null) branch.WelcomeText = string.IsNullOrWhiteSpace(request.WelcomeText) ? null : request.WelcomeText.Trim();
        if (request.AccentColor is not null) branch.AccentColor = string.IsNullOrWhiteSpace(request.AccentColor) ? null : request.AccentColor.Trim();
        if (request.WifiPassword is not null) branch.WifiPassword = string.IsNullOrWhiteSpace(request.WifiPassword) ? null : request.WifiPassword.Trim();
        if (request.TaxRate.HasValue) branch.TaxRate = request.TaxRate.Value;

        branch.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(ct);

        var mgr = await _db.Employees.AsNoTracking()
            .FirstOrDefaultAsync(e => e.CafeId == cafeId && e.BranchId == branchId
                && e.Role == EmployeeRole.Manager && e.DeletedAt == null, ct);

        return Ok(new ApiResponse<BranchDto>(true, ToDto(branch, mgr?.Phone, mgr?.Name, DateTime.UtcNow)));
    }

    [HttpDelete("{branchId}")]
    public async Task<IActionResult> Delete(
        string cafeId,
        string branchId,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsureOwner(tenant) is { } ownerDenied) return ownerDenied;

        var (ok, code, message) = await _lifecycle.ScheduleDeletionAsync(cafeId, branchId, ct);
        if (!ok)
        {
            return code switch
            {
                "NOT_FOUND" => NotFoundError(),
                "LAST_BRANCH" => BadRequest(new ApiResponse<object>(false, null,
                    new ApiError(code, message ?? "Cannot delete the last branch."))),
                _ => BadRequest(new ApiResponse<object>(false, null,
                    new ApiError(code ?? "DELETE_FAILED", message ?? "Could not delete branch.")))
            };
        }

        var branch = await _db.Branches
            .IgnoreQueryFilters()
            .FirstAsync(b => b.Id == branchId && b.CafeId == cafeId, ct);
        var mgr = await _db.Employees.AsNoTracking()
            .IgnoreQueryFilters()
            .FirstOrDefaultAsync(e => e.CafeId == cafeId && e.BranchId == branchId
                && e.Role == EmployeeRole.Manager && e.DeletedAt == branch.DeletedAt, ct);

        return Ok(new ApiResponse<BranchDto>(true,
            ToDto(branch, mgr?.Phone, mgr?.Name, DateTime.UtcNow)));
    }

    [HttpPost("{branchId}/restore")]
    public async Task<IActionResult> Restore(
        string cafeId,
        string branchId,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsureOwner(tenant) is { } ownerDenied) return ownerDenied;

        var (ok, code, message) = await _lifecycle.RestoreAsync(cafeId, branchId, ct);
        if (!ok)
        {
            return code switch
            {
                "NOT_FOUND" => NotFoundError(),
                "PURGE_EXPIRED" => BadRequest(new ApiResponse<object>(false, null,
                    new ApiError(code, message ?? "Recovery period has ended."))),
                _ => BadRequest(new ApiResponse<object>(false, null,
                    new ApiError(code ?? "RESTORE_FAILED", message ?? "Could not restore branch.")))
            };
        }

        var branch = await _db.Branches.FirstAsync(b => b.Id == branchId && b.CafeId == cafeId, ct);
        var mgr = await _db.Employees.AsNoTracking()
            .FirstOrDefaultAsync(e => e.CafeId == cafeId && e.BranchId == branchId
                && e.Role == EmployeeRole.Manager && e.DeletedAt == null, ct);

        return Ok(new ApiResponse<BranchDto>(true, ToDto(branch, mgr?.Phone, mgr?.Name, DateTime.UtcNow)));
    }

    private static BranchDto ToDto(Branch b, string? loginPhone, string? managerName, DateTime utcNow)
    {
        var pending = b.DeletedAt is not null
            && b.ScheduledPermanentDeleteAt is not null
            && b.ScheduledPermanentDeleteAt > utcNow;
        int? daysLeft = pending
            ? Math.Max(0, (int)Math.Ceiling((b.ScheduledPermanentDeleteAt!.Value - utcNow).TotalDays))
            : null;

        return new BranchDto(
            b.Id,
            b.Name,
            b.Address,
            b.City,
            b.Phone,
            b.IsActive && !pending,
            loginPhone,
            managerName,
            pending,
            b.DeletedAt,
            b.ScheduledPermanentDeleteAt,
            daysLeft);
    }
}
