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

namespace PowderCoating.Web.Controllers;

[Authorize(Policy = AppConstants.Policies.CanViewData)]
public class JournalEntriesController : Controller
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ITenantContext _tenantContext;
    private readonly IAccountBalanceService _accountBalanceService;

    public JournalEntriesController(
        IUnitOfWork unitOfWork,
        ITenantContext tenantContext,
        IAccountBalanceService accountBalanceService)
    {
        _unitOfWork = unitOfWork;
        _tenantContext = tenantContext;
        _accountBalanceService = accountBalanceService;
    }

    private bool AllowAccounting() =>
        User.IsInRole("SuperAdmin") || User.IsInRole("Administrator") || User.IsInRole("Manager");

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

    public async Task<IActionResult> Index(string? status)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

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

        var all = (await _unitOfWork.JournalEntries.FindAsync(
            je => je.CompanyId == companyId))
            .OrderByDescending(je => je.EntryDate)
            .ThenByDescending(je => je.Id)
            .ToList();

        var displayed = status switch
        {
            "Draft"  => all.Where(je => je.Status == JournalEntryStatus.Draft).ToList(),
            "Posted" => all.Where(je => je.Status == JournalEntryStatus.Posted).ToList(),
            _        => all
        };

        ViewBag.StatusFilter = status ?? "All";
        ViewBag.TotalCount   = all.Count;
        ViewBag.DraftCount   = all.Count(je => je.Status == JournalEntryStatus.Draft);
        ViewBag.PostedCount  = all.Count(je => je.Status == JournalEntryStatus.Posted);

        return View(displayed);
    }

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

    [Authorize(Policy = AppConstants.Policies.CanManageJobs)]
    public async Task<IActionResult> Create()
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");
        await PopulateAccountDropdownAsync();
        return View(new JournalEntry { EntryDate = DateTime.Today });
    }

    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CanManageJobs)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(
        JournalEntry model,
        int[] lineAccountIds,
        decimal[] lineDebits,
        decimal[] lineCreditAmounts,
        string?[] lineDescriptions,
        int[] lineOrders)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

        var lines = BuildLines(lineAccountIds, lineDebits, lineCreditAmounts, lineDescriptions, lineOrders);

        if (!ValidateLines(lines, out string? error))
        {
            TempData["Error"] = error;
            await PopulateAccountDropdownAsync();
            return View(model);
        }

        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
        model.EntryNumber = await GenerateEntryNumberAsync(companyId);
        model.Status = JournalEntryStatus.Draft;
        model.Lines  = lines;

        await _unitOfWork.JournalEntries.AddAsync(model);
        await _unitOfWork.CompleteAsync();

        TempData["Success"] = $"Journal entry {model.EntryNumber} created as draft.";
        return RedirectToAction(nameof(Details), new { id = model.Id });
    }

    // ── Details ──────────────────────────────────────────────────────────────

    public async Task<IActionResult> Details(int id)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

        var je = (await _unitOfWork.JournalEntries.FindAsync(
            e => e.Id == id,
            false,
            e => e.Lines))
            .FirstOrDefault();

        if (je == null) return NotFound();

        // Load account names for lines
        var accountIds = je.Lines.Select(l => l.AccountId).Distinct().ToList();
        var accounts   = await _unitOfWork.Accounts.FindAsync(a => a.CompanyId == je.CompanyId && accountIds.Contains(a.Id));
        ViewBag.AccountMap = accounts.ToDictionary(a => a.Id, a => $"{a.AccountNumber} – {a.Name}");

        // Reversal metadata
        if (je.ReversalOfId.HasValue)
        {
            var original = await _unitOfWork.JournalEntries.GetByIdAsync(je.ReversalOfId.Value);
            ViewBag.ReversalOfNumber = original?.EntryNumber;
        }

        var reversal = (await _unitOfWork.JournalEntries.FindAsync(
            r => r.ReversalOfId == je.Id && r.Status == JournalEntryStatus.Posted))
            .FirstOrDefault();
        ViewBag.ReversalEntryNumber = reversal?.EntryNumber;
        ViewBag.ReversalEntryId     = reversal?.Id;

        return View(je);
    }

    // ── Post ─────────────────────────────────────────────────────────────────

    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CanManageJobs)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Post(int id)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

        var entry = (await _unitOfWork.JournalEntries.FindAsync(
            je => je.Id == id,
            false,
            je => je.Lines))
            .FirstOrDefault();

        if (entry == null) return NotFound();
        if (entry.Status != JournalEntryStatus.Draft)
        {
            TempData["Error"] = "Only draft entries can be posted.";
            return RedirectToAction(nameof(Details), new { id });
        }

        var totalDebits  = entry.Lines.Sum(l => l.DebitAmount);
        var totalCredits = entry.Lines.Sum(l => l.CreditAmount);
        if (totalDebits != totalCredits)
        {
            TempData["Error"] = $"Entry does not balance — debits {totalDebits:C} ≠ credits {totalCredits:C}.";
            return RedirectToAction(nameof(Details), new { id });
        }

        // Period lock check — block posting if the entry date falls in a locked period
        var company = await _unitOfWork.Companies.GetByIdAsync(entry.CompanyId);
        if (Web.Helpers.AccountingPeriodValidator.IsLocked(entry.EntryDate, company?.BookLockedThrough))
        {
            TempData["Error"] = Web.Helpers.AccountingPeriodValidator.LockedMessage(company!.BookLockedThrough);
            return RedirectToAction(nameof(Details), new { id });
        }

        await _unitOfWork.ExecuteInTransactionAsync(async () =>
        {
            entry.Status   = JournalEntryStatus.Posted;
            entry.PostedAt = DateTime.UtcNow;
            entry.PostedBy = User.Identity?.Name;

            foreach (var line in entry.Lines)
            {
                if (line.DebitAmount > 0)
                    await _accountBalanceService.DebitAsync(line.AccountId, line.DebitAmount);
                if (line.CreditAmount > 0)
                    await _accountBalanceService.CreditAsync(line.AccountId, line.CreditAmount);
            }

            await _unitOfWork.CompleteAsync();
        });

        TempData["Success"] = $"Journal entry {entry.EntryNumber} posted successfully.";
        return RedirectToAction(nameof(Details), new { id });
    }

    // ── Sales Tax Remittance ───────────────────────────────────────────────────

    /// <summary>
    /// Form to record a sales tax payment to the tax authority. Shows the current Sales Tax Payable
    /// (2200) liability and a bank-account picker. Relieves the liability that invoices accumulate.
    /// </summary>
    // GET: /JournalEntries/SalesTaxPayment
    public async Task<IActionResult> SalesTaxPayment()
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");
        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;

        var taxAcct = (await _unitOfWork.Accounts.FindAsync(
            a => a.CompanyId == companyId && a.AccountNumber == "2200" && a.IsActive)).FirstOrDefault();
        ViewBag.TaxLiability = taxAcct?.CurrentBalance ?? 0m;
        ViewBag.TaxAccountFound = taxAcct != null;

        var banks = await _unitOfWork.Accounts.FindAsync(a => a.CompanyId == companyId && a.IsActive
            && (a.AccountSubType == AccountSubType.Checking
             || a.AccountSubType == AccountSubType.Savings
             || a.AccountSubType == AccountSubType.Cash));
        ViewBag.BankAccounts = banks.OrderBy(a => a.AccountNumber)
            .Select(a => new SelectListItem($"{a.AccountNumber} – {a.Name}", a.Id.ToString())).ToList();

        return View();
    }

    /// <summary>
    /// Records a sales tax remittance as a posted journal entry: DR Sales Tax Payable (2200) / CR the
    /// chosen bank account. Honors the period lock. The reporting already accounts for posted JE lines,
    /// so this is all that's needed to relieve the liability.
    /// </summary>
    // POST: /JournalEntries/SalesTaxPayment
    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CanManageJobs)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> SalesTaxPayment(decimal amount, DateTime paymentDate, int bankAccountId, string? reference)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");
        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;

        if (amount <= 0)
        {
            TempData["Error"] = "Enter a payment amount greater than zero.";
            return RedirectToAction(nameof(SalesTaxPayment));
        }

        var taxAcct = (await _unitOfWork.Accounts.FindAsync(
            a => a.CompanyId == companyId && a.AccountNumber == "2200" && a.IsActive)).FirstOrDefault();
        if (taxAcct == null)
        {
            TempData["Error"] = "No active Sales Tax Payable (2200) account found in your chart of accounts.";
            return RedirectToAction(nameof(SalesTaxPayment));
        }

        // Don't let a remittance exceed the outstanding liability — overpaying would push Sales Tax
        // Payable into an abnormal (debit) balance. The 0.005 tolerance absorbs decimal rounding.
        if (amount > taxAcct.CurrentBalance + 0.005m)
        {
            TempData["Error"] = $"Payment of {amount:C} exceeds the Sales Tax Payable balance of {taxAcct.CurrentBalance:C}. Enter an amount up to the outstanding liability.";
            return RedirectToAction(nameof(SalesTaxPayment));
        }

        var bankAcct = await _unitOfWork.Accounts.GetByIdAsync(bankAccountId);
        if (bankAcct == null || bankAcct.CompanyId != companyId)
        {
            TempData["Error"] = "Select a valid bank account to pay from.";
            return RedirectToAction(nameof(SalesTaxPayment));
        }

        var company = await _unitOfWork.Companies.GetByIdAsync(companyId);
        if (Web.Helpers.AccountingPeriodValidator.IsLocked(paymentDate, company?.BookLockedThrough))
        {
            TempData["Error"] = Web.Helpers.AccountingPeriodValidator.LockedMessage(company!.BookLockedThrough);
            return RedirectToAction(nameof(SalesTaxPayment));
        }

        var entryNumber = await GenerateEntryNumberAsync(companyId);
        var entry = new JournalEntry
        {
            EntryNumber = entryNumber,
            EntryDate   = paymentDate,
            Reference   = string.IsNullOrWhiteSpace(reference) ? "Sales tax remittance" : reference.Trim(),
            Description = $"Sales tax remittance — {amount:C}",
            Status      = JournalEntryStatus.Posted,
            PostedAt    = DateTime.UtcNow,
            PostedBy    = User.Identity?.Name,
            CompanyId   = companyId,
            Lines = new List<JournalEntryLine>
            {
                new() { AccountId = taxAcct.Id,  DebitAmount = amount, CreditAmount = 0, Description = "Sales tax paid to authority", LineOrder = 0, CompanyId = companyId },
                new() { AccountId = bankAcct.Id, DebitAmount = 0, CreditAmount = amount, Description = $"Paid from {bankAcct.Name}", LineOrder = 1, CompanyId = companyId }
            }
        };

        await _unitOfWork.ExecuteInTransactionAsync(async () =>
        {
            await _unitOfWork.JournalEntries.AddAsync(entry);
            await _accountBalanceService.DebitAsync(taxAcct.Id, amount);    // reduce the liability
            await _accountBalanceService.CreditAsync(bankAcct.Id, amount);  // cash leaves the bank
            await _unitOfWork.CompleteAsync();
        });

        TempData["Success"] = $"Recorded sales tax remittance of {amount:C} ({entryNumber}).";
        return RedirectToAction(nameof(Details), new { id = entry.Id });
    }

    // ── Reverse ──────────────────────────────────────────────────────────────

    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CanManageJobs)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Reverse(int id)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

        var original = (await _unitOfWork.JournalEntries.FindAsync(
            je => je.Id == id,
            false,
            je => je.Lines))
            .FirstOrDefault();

        if (original == null) return NotFound();
        if (original.Status != JournalEntryStatus.Posted)
        {
            TempData["Error"] = "Only posted entries can be reversed.";
            return RedirectToAction(nameof(Details), new { id });
        }

        var existingReversal = (await _unitOfWork.JournalEntries.FindAsync(
            je => je.ReversalOfId == id))
            .FirstOrDefault();
        if (existingReversal != null)
        {
            TempData["Error"] = $"This entry was already reversed by {existingReversal.EntryNumber}.";
            return RedirectToAction(nameof(Details), new { id });
        }

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

        await _unitOfWork.ExecuteInTransactionAsync(async () =>
        {
            var reversal = new JournalEntry
            {
                EntryNumber  = await GenerateEntryNumberAsync(companyId),
                EntryDate    = DateTime.Today,
                Reference    = $"Reversal of {original.EntryNumber}",
                Description  = $"Reversal of {original.EntryNumber}: {original.Description}",
                Status       = JournalEntryStatus.Posted,
                IsReversal   = true,
                ReversalOfId = original.Id,
                PostedAt     = DateTime.UtcNow,
                PostedBy     = User.Identity?.Name,
                Lines = original.Lines.Select((l, i) => new JournalEntryLine
                {
                    AccountId    = l.AccountId,
                    DebitAmount  = l.CreditAmount,
                    CreditAmount = l.DebitAmount,
                    Description  = l.Description,
                    LineOrder    = l.LineOrder
                }).ToList()
            };

            await _unitOfWork.JournalEntries.AddAsync(reversal);
            original.Status = JournalEntryStatus.Reversed;

            foreach (var line in reversal.Lines)
            {
                if (line.DebitAmount > 0)
                    await _accountBalanceService.DebitAsync(line.AccountId, line.DebitAmount);
                if (line.CreditAmount > 0)
                    await _accountBalanceService.CreditAsync(line.AccountId, line.CreditAmount);
            }

            await _unitOfWork.CompleteAsync();
            newEntryId = reversal.Id;
        });

        TempData["Success"] = "Reversal entry created and posted.";
        return RedirectToAction(nameof(Details), new { id = newEntryId });
    }

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

    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CompanyAdminOnly)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Delete(int id)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

        var entry = await _unitOfWork.JournalEntries.GetByIdAsync(id);
        if (entry == null) return NotFound();

        if (entry.Status != JournalEntryStatus.Draft)
        {
            TempData["Error"] = "Only draft entries can be deleted. Posted entries must be reversed.";
            return RedirectToAction(nameof(Details), new { id });
        }

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

        TempData["Success"] = $"Journal entry {entry.EntryNumber} deleted.";
        return RedirectToAction(nameof(Index));
    }

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

    private static List<JournalEntryLine> BuildLines(
        int[] accountIds, decimal[] debits, decimal[] credits,
        string?[] descriptions, int[] orders)
    {
        var lines = new List<JournalEntryLine>();
        for (int i = 0; i < accountIds.Length; i++)
        {
            if (accountIds[i] == 0) continue;
            lines.Add(new JournalEntryLine
            {
                AccountId    = accountIds[i],
                DebitAmount  = i < debits.Length   ? debits[i]   : 0,
                CreditAmount = i < credits.Length  ? credits[i]  : 0,
                Description  = i < descriptions.Length ? descriptions[i] : null,
                LineOrder    = i < orders.Length   ? orders[i]   : i
            });
        }
        return lines;
    }

    private static bool ValidateLines(List<JournalEntryLine> lines, out string? error)
    {
        if (lines.Count < 2)
        {
            error = "A journal entry must have at least two lines.";
            return false;
        }
        var totalDebits  = lines.Sum(l => l.DebitAmount);
        var totalCredits = lines.Sum(l => l.CreditAmount);
        if (totalDebits == 0 && totalCredits == 0)
        {
            error = "At least one debit or credit amount must be non-zero.";
            return false;
        }
        if (totalDebits != totalCredits)
        {
            error = $"Debits ({totalDebits:C}) must equal credits ({totalCredits:C}) before saving.";
            return false;
        }
        error = null;
        return true;
    }

    /// <summary>
    /// Generates the next sequential entry number in the format JE-YYMM-####.
    /// Queries across soft-deleted entries to prevent number reuse after deletion.
    /// </summary>
    private async Task<string> GenerateEntryNumberAsync(int companyId)
    {
        var prefix = $"JE-{DateTime.Now:yyMM}-";
        var all = await _unitOfWork.JournalEntries.FindAsync(
            je => je.CompanyId == companyId && je.EntryNumber.StartsWith(prefix),
            ignoreQueryFilters: true);

        int next = 1;
        if (all.Any())
        {
            var nums = all
                .Select(je => je.EntryNumber[prefix.Length..])
                .Select(s => int.TryParse(s, out int n) ? n : 0);
            next = nums.Max() + 1;
        }

        return $"{prefix}{next:D4}";
    }

    private async Task PopulateAccountDropdownAsync()
    {
        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
        var accounts = await _unitOfWork.Accounts.FindAsync(a => a.CompanyId == companyId && a.IsActive);
        ViewBag.AccountSelectList = accounts
            .OrderBy(a => a.AccountNumber)
            .Select(a => new SelectListItem
            {
                Value = a.Id.ToString(),
                Text  = $"{a.AccountNumber} – {a.Name}"
            })
            .ToList();
    }
}
