using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using PowderCoating.Application.DTOs.AI;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Enums;
using PowderCoating.Core.Interfaces;
using PowderCoating.Shared.Constants;
using PowderCoating.Web.Helpers;

namespace PowderCoating.Web.Controllers;

[Authorize(Policy = AppConstants.Policies.CanViewData)]
public class BankReconciliationsController : Controller
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ITenantContext _tenantContext;
    private readonly IAccountingAiService _accountingAi;
    private readonly IAiUsageLogger _usageLogger;

    public BankReconciliationsController(
        IUnitOfWork unitOfWork,
        ITenantContext tenantContext,
        IAccountingAiService accountingAi,
        IAiUsageLogger usageLogger)
    {
        _unitOfWork = unitOfWork;
        _tenantContext = tenantContext;
        _accountingAi = accountingAi;
        _usageLogger = usageLogger;
    }

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

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

    /// <summary>Lists all reconciliation sessions for the company, newest first.</summary>
    public async Task<IActionResult> Index()
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

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

        var all = (await _unitOfWork.BankReconciliations.FindAsync(
            br => br.CompanyId == companyId,
            false,
            br => br.Account))
            .OrderByDescending(br => br.StatementDate)
            .ThenByDescending(br => br.Id)
            .ToList();

        return View(all);
    }

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

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

    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(BankReconciliation model)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

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

        // The account being reconciled must be a real money account (Asset/Liability).
        if (!await AccountGuard.IsValidMoneyAccountAsync(_unitOfWork, model.AccountId, companyId))
        {
            TempData["Error"] = "Select a valid bank, cash, or credit account to reconcile.";
            return RedirectToAction(nameof(Create));
        }

        // Set beginning balance from last completed reconciliation for this account, or 0
        var lastCompleted = (await _unitOfWork.BankReconciliations.FindAsync(
            br => br.CompanyId == companyId
               && br.AccountId == model.AccountId
               && br.Status == BankReconciliationStatus.Completed))
            .OrderByDescending(br => br.StatementDate)
            .FirstOrDefault();

        model.BeginningBalance = lastCompleted?.EndingBalance ?? 0;
        model.Status = BankReconciliationStatus.InProgress;

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

        TempData["Success"] = "Reconciliation started.";
        return RedirectToAction(nameof(Reconcile), new { id = model.Id });
    }

    // ── Reconcile (Working View) ──────────────────────────────────────────────

    /// <summary>
    /// Main working view. Shows all uncleared transactions for the account up to StatementDate
    /// in two sections (deposits/credits and payments/debits) with checkboxes.
    /// Running cleared balance and difference update via JS as the user checks items.
    /// </summary>
    public async Task<IActionResult> Reconcile(int id)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

        var recon = (await _unitOfWork.BankReconciliations.FindAsync(
            br => br.Id == id,
            false,
            br => br.Account))
            .FirstOrDefault();

        if (recon == null) return NotFound();
        if (recon.Status == BankReconciliationStatus.Completed)
            return RedirectToAction(nameof(Report), new { id });

        var accountId     = recon.AccountId;
        var statementDate = recon.StatementDate;

        // Customer payments deposited to this account
        var deposits = (await _unitOfWork.Payments.FindAsync(
            p => p.DepositAccountId == accountId && p.PaymentDate <= statementDate))
            .Select(p => new ReconciliationItem
            {
                EntityType  = "Payment",
                EntityId    = p.Id,
                Date        = p.PaymentDate,
                Reference   = p.Reference ?? $"PMT-{p.Id}",
                Description = $"Payment #{p.InvoiceId}",
                Amount      = p.Amount,
                IsCleared   = p.IsCleared
            }).ToList();

        // Bill payments out of this account (debits — shown as negative in deposits)
        var billPayments = (await _unitOfWork.BillPayments.FindAsync(
            bp => bp.BankAccountId == accountId && bp.PaymentDate <= statementDate))
            .Select(bp => new ReconciliationItem
            {
                EntityType  = "BillPayment",
                EntityId    = bp.Id,
                Date        = bp.PaymentDate,
                Reference   = bp.PaymentNumber,
                Description = bp.Memo ?? bp.BillId.ToString(),
                Amount      = bp.Amount,
                IsCleared   = bp.IsCleared
            }).ToList();

        // Direct expenses out of this account
        var expenses = (await _unitOfWork.Expenses.FindAsync(
            e => e.PaymentAccountId == accountId && e.Date <= statementDate))
            .Select(e => new ReconciliationItem
            {
                EntityType  = "Expense",
                EntityId    = e.Id,
                Date        = e.Date,
                Reference   = e.ExpenseNumber,
                Description = e.Memo ?? string.Empty,
                Amount      = e.Amount,
                IsCleared   = e.IsCleared
            }).ToList();

        ViewBag.Recon      = recon;
        ViewBag.Deposits   = deposits;
        ViewBag.Payments   = billPayments.Concat(expenses).OrderBy(p => p.Date).ToList();

        return View();
    }

    // ── ToggleCleared (AJAX) ─────────────────────────────────────────────────

    /// <summary>
    /// AJAX endpoint. Marks a Payment, BillPayment, or Expense as cleared/uncleared.
    /// Returns updated running totals as JSON.
    /// </summary>
    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ToggleCleared(
        int reconId, string entityType, int entityId, bool isCleared)
    {
        if (!AllowAccounting()) return Forbid();

        var recon = await _unitOfWork.BankReconciliations.GetByIdAsync(reconId);
        if (recon == null) return NotFound();

        var now = isCleared ? DateTime.UtcNow : (DateTime?)null;

        switch (entityType)
        {
            case "Payment":
                var payment = await _unitOfWork.Payments.GetByIdAsync(entityId);
                if (payment != null) { payment.IsCleared = isCleared; payment.ClearedDate = now; }
                break;
            case "BillPayment":
                var bp = await _unitOfWork.BillPayments.GetByIdAsync(entityId);
                if (bp != null) { bp.IsCleared = isCleared; bp.ClearedDate = now; }
                break;
            case "Expense":
                var exp = await _unitOfWork.Expenses.GetByIdAsync(entityId);
                if (exp != null) { exp.IsCleared = isCleared; exp.ClearedDate = now; }
                break;
        }

        await _unitOfWork.CompleteAsync();
        return Ok(new { success = true });
    }

    // ── Complete ─────────────────────────────────────────────────────────────

    /// <summary>Completes the reconciliation. Only allowed when Difference == 0.00.</summary>
    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Complete(int id, decimal difference)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

        if (Math.Abs(difference) > 0.005m)
        {
            TempData["Error"] = $"Cannot complete: difference is {difference:C}. Must be $0.00.";
            return RedirectToAction(nameof(Reconcile), new { id });
        }

        var recon = await _unitOfWork.BankReconciliations.GetByIdAsync(id);
        if (recon == null) return NotFound();

        recon.Status      = BankReconciliationStatus.Completed;
        recon.CompletedAt = DateTime.UtcNow;
        recon.CompletedBy = User.Identity?.Name;
        await _unitOfWork.CompleteAsync();

        TempData["Success"] = "Reconciliation completed.";
        return RedirectToAction(nameof(Report), new { id });
    }

    // ── Report ────────────────────────────────────────────────────────────────

    /// <summary>Printable view of a completed reconciliation.</summary>
    public async Task<IActionResult> Report(int id)
    {
        if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");

        var recon = (await _unitOfWork.BankReconciliations.FindAsync(
            br => br.Id == id,
            false,
            br => br.Account))
            .FirstOrDefault();

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

        var accountId = recon.AccountId;

        var clearedDeposits = (await _unitOfWork.Payments.FindAsync(
            p => p.DepositAccountId == accountId && p.IsCleared && p.PaymentDate <= recon.StatementDate))
            .ToList();

        var clearedPayments = new List<ReconciliationItem>();
        (await _unitOfWork.BillPayments.FindAsync(
            bp => bp.BankAccountId == accountId && bp.IsCleared && bp.PaymentDate <= recon.StatementDate))
            .ToList()
            .ForEach(bp => clearedPayments.Add(new ReconciliationItem
            {
                EntityType  = "BillPayment", EntityId = bp.Id, Date = bp.PaymentDate,
                Reference   = bp.PaymentNumber, Amount = bp.Amount, IsCleared = true
            }));
        (await _unitOfWork.Expenses.FindAsync(
            e => e.PaymentAccountId == accountId && e.IsCleared && e.Date <= recon.StatementDate))
            .ToList()
            .ForEach(e => clearedPayments.Add(new ReconciliationItem
            {
                EntityType  = "Expense", EntityId = e.Id, Date = e.Date,
                Reference   = e.ExpenseNumber, Amount = e.Amount, IsCleared = true
            }));

        ViewBag.ClearedDeposits = clearedDeposits;
        ViewBag.ClearedPayments = clearedPayments.OrderBy(p => p.Date).ToList();

        return View(recon);
    }

    // ── AI Auto-Match (AJAX) ──────────────────────────────────────────────────

    /// <summary>
    /// AJAX endpoint. Passes uncleared bank rec items to Claude and returns suggested items
    /// to mark as cleared. The controller assembles all three transaction types (deposits,
    /// bill payments, expenses) for the reconciliation's account, then delegates scoring to
    /// <see cref="IAccountingAiService.AutoMatchReconciliationAsync"/>. The caller applies
    /// suggestions client-side by auto-checking the corresponding table rows.
    /// </summary>
    [HttpPost]
    [Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> AiSuggestMatches(int reconId)
    {
        if (!AllowAccounting()) return Forbid();

        var recon = (await _unitOfWork.BankReconciliations.FindAsync(
            br => br.Id == reconId, false, br => br.Account))
            .FirstOrDefault();
        if (recon == null) return NotFound();

        var accountId     = recon.AccountId;
        var statementDate = recon.StatementDate;

        var items = new List<BankRecMatchItem>();

        (await _unitOfWork.Payments.FindAsync(
            p => p.DepositAccountId == accountId && p.PaymentDate <= statementDate && !p.IsCleared))
            .ToList()
            .ForEach(p => items.Add(new BankRecMatchItem
            {
                EntityType  = "Payment",
                EntityId    = p.Id,
                Date        = p.PaymentDate.ToString("yyyy-MM-dd"),
                Reference   = p.Reference ?? $"PMT-{p.Id}",
                Description = $"Payment #{p.InvoiceId}",
                Amount      = p.Amount,
                Direction   = "deposit"
            }));

        (await _unitOfWork.BillPayments.FindAsync(
            bp => bp.BankAccountId == accountId && bp.PaymentDate <= statementDate && !bp.IsCleared))
            .ToList()
            .ForEach(bp => items.Add(new BankRecMatchItem
            {
                EntityType  = "BillPayment",
                EntityId    = bp.Id,
                Date        = bp.PaymentDate.ToString("yyyy-MM-dd"),
                Reference   = bp.PaymentNumber,
                Description = bp.Memo ?? bp.BillId.ToString(),
                Amount      = bp.Amount,
                Direction   = "payment"
            }));

        (await _unitOfWork.Expenses.FindAsync(
            e => e.PaymentAccountId == accountId && e.Date <= statementDate && !e.IsCleared))
            .ToList()
            .ForEach(e => items.Add(new BankRecMatchItem
            {
                EntityType  = "Expense",
                EntityId    = e.Id,
                Date        = e.Date.ToString("yyyy-MM-dd"),
                Reference   = e.ExpenseNumber,
                Description = e.Memo ?? string.Empty,
                Amount      = e.Amount,
                Direction   = "payment"
            }));

        if (!items.Any())
            return Json(new { success = false, errorMessage = "No uncleared transactions to analyze." });

        var request = new AutoMatchRequest
        {
            UnclearedItems        = items,
            BeginningBalance      = recon.BeginningBalance,
            StatementEndingBalance = recon.EndingBalance
        };

        var result = await _accountingAi.AutoMatchReconciliationAsync(request);
        var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "";
        await _usageLogger.LogAsync(recon.CompanyId, userId, AppConstants.AiFeatures.BankRecAutoMatch, result.Success);

        return Json(result);
    }

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

    private async Task PopulateAccountDropdownAsync()
    {
        var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
        // Reconcilable accounts: any Asset (bank/cash) or Liability (credit card, line of
        // credit) account. Filter by parent AccountType, not sub-type, so an account the
        // company classified differently still shows up for reconciliation.
        var accounts = await _unitOfWork.Accounts.FindAsync(
            a => a.CompanyId == companyId && a.IsActive
              && (a.AccountType == AccountType.Asset
               || a.AccountType == AccountType.Liability));

        ViewBag.AccountSelectList = accounts
            .OrderBy(a => a.AccountNumber)
            .Select(a => new SelectListItem
            {
                Value = a.Id.ToString(),
                Text  = $"{a.AccountNumber} – {a.Name}"
            })
            .ToList();
    }
}

/// <summary>View model for a single reconcileable transaction row.</summary>
public class ReconciliationItem
{
    public string EntityType { get; set; } = string.Empty;
    public int    EntityId   { get; set; }
    public DateTime Date     { get; set; }
    public string Reference  { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public decimal Amount    { get; set; }
    public bool IsCleared    { get; set; }
}
