using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Enums;
using PowderCoating.Core.Interfaces;
using PowderCoating.Shared.Constants;

namespace PowderCoating.Web.Controllers;

/// <summary>
/// Manages annual budgets. Each budget has one BudgetLine per active GL account with
/// monthly amounts (Jan–Dec). The Budget vs. Actual report compares these to real activity.
/// Only one budget per year is marked IsDefault — that one feeds the variance report automatically.
/// </summary>
[Authorize(Policy = AppConstants.Policies.CompanyAdminOnly)]
public class BudgetsController : Controller
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ITenantContext _tenantContext;

    public BudgetsController(IUnitOfWork unitOfWork, ITenantContext tenantContext)
    {
        _unitOfWork   = unitOfWork;
        _tenantContext = tenantContext;
    }

    // ── Index ─────────────────────────────────────────────────────────────────

    /// <summary>Lists all budgets for the current company ordered by fiscal year descending.</summary>
    public async Task<IActionResult> Index()
    {
        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
        var budgets = (await _unitOfWork.Budgets.FindAsync(b => b.CompanyId == companyId, false, b => b.Lines))
            .OrderByDescending(b => b.FiscalYear)
            .ThenBy(b => b.Name)
            .ToList();

        return View(budgets);
    }

    // ── Create ────────────────────────────────────────────────────────────────

    [HttpGet]
    public async Task<IActionResult> Create()
    {
        var accounts = await GetBudgetableAccountsAsync();
        return View(new BudgetCreateVm
        {
            FiscalYear = DateTime.Now.Year,
            Lines = accounts.Select(a => new BudgetLineVm { AccountId = a.Id, AccountNumber = a.AccountNumber, AccountName = a.Name, AccountType = a.AccountType }).ToList()
        });
    }

    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(BudgetCreateVm vm)
    {
        if (!ModelState.IsValid) return View(vm);

        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;

        // If this is marked default, clear the flag on other budgets for the same year
        if (vm.IsDefault)
            await ClearDefaultFlagAsync(companyId, vm.FiscalYear, excludeId: null);

        var budget = new Budget
        {
            Name       = vm.Name,
            FiscalYear = vm.FiscalYear,
            Notes      = vm.Notes,
            IsDefault  = vm.IsDefault,
            CompanyId  = companyId,
            CreatedAt  = DateTime.UtcNow,
            Lines      = vm.Lines
                .Where(l => l.HasAnyAmount)
                .Select(l => new BudgetLine
                {
                    AccountId = l.AccountId,
                    Jan = l.Jan, Feb = l.Feb, Mar = l.Mar, Apr = l.Apr,
                    May = l.May, Jun = l.Jun, Jul = l.Jul, Aug = l.Aug,
                    Sep = l.Sep, Oct = l.Oct, Nov = l.Nov, Dec = l.Dec,
                    CompanyId = companyId, CreatedAt = DateTime.UtcNow
                }).ToList()
        };

        await _unitOfWork.Budgets.AddAsync(budget);
        await _unitOfWork.CompleteAsync();

        TempData["Success"] = $"Budget \"{budget.Name}\" created for {budget.FiscalYear}.";
        return RedirectToAction(nameof(Edit), new { id = budget.Id });
    }

    // ── Edit ──────────────────────────────────────────────────────────────────

    [HttpGet]
    public async Task<IActionResult> Edit(int id)
    {
        var budget = await _unitOfWork.Budgets.GetByIdAsync(id, false, b => b.Lines);
        if (budget == null) return NotFound();

        var accounts = await GetBudgetableAccountsAsync();
        var lineMap  = budget.Lines.ToDictionary(l => l.AccountId);

        var vm = new BudgetCreateVm
        {
            Id         = budget.Id,
            Name       = budget.Name,
            FiscalYear = budget.FiscalYear,
            Notes      = budget.Notes,
            IsDefault  = budget.IsDefault,
            Lines      = accounts.Select(a =>
            {
                lineMap.TryGetValue(a.Id, out var existing);
                return new BudgetLineVm
                {
                    AccountId     = a.Id,
                    AccountNumber = a.AccountNumber,
                    AccountName   = a.Name,
                    AccountType   = a.AccountType,
                    Jan = existing?.Jan ?? 0, Feb = existing?.Feb ?? 0, Mar = existing?.Mar ?? 0,
                    Apr = existing?.Apr ?? 0, May = existing?.May ?? 0, Jun = existing?.Jun ?? 0,
                    Jul = existing?.Jul ?? 0, Aug = existing?.Aug ?? 0, Sep = existing?.Sep ?? 0,
                    Oct = existing?.Oct ?? 0, Nov = existing?.Nov ?? 0, Dec = existing?.Dec ?? 0
                };
            }).ToList()
        };

        return View(vm);
    }

    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, BudgetCreateVm vm)
    {
        if (id != vm.Id) return BadRequest();
        if (!ModelState.IsValid) return View(vm);

        var budget = await _unitOfWork.Budgets.GetByIdAsync(id, false, b => b.Lines);
        if (budget == null) return NotFound();

        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;

        if (vm.IsDefault && !budget.IsDefault)
            await ClearDefaultFlagAsync(companyId, vm.FiscalYear, excludeId: id);

        budget.Name      = vm.Name;
        budget.Notes     = vm.Notes;
        budget.IsDefault = vm.IsDefault;
        budget.UpdatedAt = DateTime.UtcNow;

        // Delete old lines and replace with new set (simpler than merge)
        foreach (var line in budget.Lines.ToList())
            await _unitOfWork.BudgetLines.SoftDeleteAsync(line.Id);

        budget.Lines = vm.Lines
            .Where(l => l.HasAnyAmount)
            .Select(l => new BudgetLine
            {
                AccountId = l.AccountId,
                Jan = l.Jan, Feb = l.Feb, Mar = l.Mar, Apr = l.Apr,
                May = l.May, Jun = l.Jun, Jul = l.Jul, Aug = l.Aug,
                Sep = l.Sep, Oct = l.Oct, Nov = l.Nov, Dec = l.Dec,
                CompanyId = companyId, CreatedAt = DateTime.UtcNow
            }).ToList();

        await _unitOfWork.CompleteAsync();

        TempData["Success"] = $"Budget \"{budget.Name}\" saved.";
        return RedirectToAction(nameof(Edit), new { id });
    }

    // ── Copy ─────────────────────────────────────────────────────────────────

    /// <summary>
    /// Creates a copy of an existing budget for a new fiscal year — common workflow for
    /// rolling forward last year's budget as a starting point.
    /// </summary>
    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Copy(int id, int newYear)
    {
        var source = await _unitOfWork.Budgets.GetByIdAsync(id, false, b => b.Lines);
        if (source == null) return NotFound();

        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
        var copy = new Budget
        {
            Name      = $"{source.Name} ({newYear})",
            FiscalYear = newYear,
            Notes     = source.Notes,
            IsDefault = false,
            CompanyId = companyId,
            CreatedAt = DateTime.UtcNow,
            Lines     = source.Lines.Select(l => new BudgetLine
            {
                AccountId = l.AccountId,
                Jan = l.Jan, Feb = l.Feb, Mar = l.Mar, Apr = l.Apr,
                May = l.May, Jun = l.Jun, Jul = l.Jul, Aug = l.Aug,
                Sep = l.Sep, Oct = l.Oct, Nov = l.Nov, Dec = l.Dec,
                CompanyId = companyId, CreatedAt = DateTime.UtcNow
            }).ToList()
        };

        await _unitOfWork.Budgets.AddAsync(copy);
        await _unitOfWork.CompleteAsync();

        TempData["Success"] = $"Budget copied to {newYear}.";
        return RedirectToAction(nameof(Edit), new { id = copy.Id });
    }

    // ── SetDefault ────────────────────────────────────────────────────────────

    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> SetDefault(int id)
    {
        var budget = await _unitOfWork.Budgets.GetByIdAsync(id);
        if (budget == null) return NotFound();

        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
        await ClearDefaultFlagAsync(companyId, budget.FiscalYear, excludeId: null);

        budget.IsDefault = true;
        budget.UpdatedAt = DateTime.UtcNow;
        await _unitOfWork.CompleteAsync();

        TempData["Success"] = $"\"{budget.Name}\" is now the default budget for {budget.FiscalYear}.";
        return RedirectToAction(nameof(Index));
    }

    // ── Delete ────────────────────────────────────────────────────────────────

    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Delete(int id)
    {
        var budget = await _unitOfWork.Budgets.GetByIdAsync(id, false, b => b.Lines);
        if (budget == null) return NotFound();

        foreach (var line in budget.Lines.ToList())
            await _unitOfWork.BudgetLines.SoftDeleteAsync(line.Id);

        await _unitOfWork.Budgets.SoftDeleteAsync(id);
        await _unitOfWork.CompleteAsync();

        TempData["Success"] = $"Budget \"{budget.Name}\" deleted.";
        return RedirectToAction(nameof(Index));
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private async Task<List<Account>> GetBudgetableAccountsAsync()
    {
        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
        var accounts = await _unitOfWork.Accounts.FindAsync(
            a => a.CompanyId == companyId && a.IsActive && (a.AccountType == AccountType.Revenue || a.AccountType == AccountType.Expense));
        return accounts.OrderBy(a => a.AccountNumber).ToList();
    }

    private async Task ClearDefaultFlagAsync(int companyId, int fiscalYear, int? excludeId)
    {
        var others = await _unitOfWork.Budgets.FindAsync(
            b => b.CompanyId == companyId && b.IsDefault && b.FiscalYear == fiscalYear && b.Id != (excludeId ?? 0));
        foreach (var b in others)
        {
            b.IsDefault = false;
            b.UpdatedAt = DateTime.UtcNow;
        }
        if (others.Any())
            await _unitOfWork.CompleteAsync();
    }
}

// ── View Models ───────────────────────────────────────────────────────────────

public class BudgetCreateVm
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public int FiscalYear { get; set; } = DateTime.Now.Year;
    public string? Notes { get; set; }
    public bool IsDefault { get; set; } = true;
    public List<BudgetLineVm> Lines { get; set; } = new();
}

public class BudgetLineVm
{
    public int AccountId { get; set; }
    public string AccountNumber { get; set; } = string.Empty;
    public string AccountName { get; set; } = string.Empty;
    public AccountType AccountType { get; set; }

    public decimal Jan { get; set; }
    public decimal Feb { get; set; }
    public decimal Mar { get; set; }
    public decimal Apr { get; set; }
    public decimal May { get; set; }
    public decimal Jun { get; set; }
    public decimal Jul { get; set; }
    public decimal Aug { get; set; }
    public decimal Sep { get; set; }
    public decimal Oct { get; set; }
    public decimal Nov { get; set; }
    public decimal Dec { get; set; }

    public decimal Annual => Jan + Feb + Mar + Apr + May + Jun + Jul + Aug + Sep + Oct + Nov + Dec;
    public bool HasAnyAmount => Annual != 0;
}
