using PowderCoating.Core.Enums;

namespace PowderCoating.Core.Entities;

/// <summary>
/// Chart of Accounts entry. Supports a flat or one-level hierarchy via ParentAccountId.
/// </summary>
public class Account : BaseEntity
{
    public string AccountNumber { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;
    public AccountType AccountType { get; set; }
    public AccountSubType AccountSubType { get; set; }
    public string? Description { get; set; }

    /// <summary>Nullable FK for sub-accounts (one level deep).</summary>
    public int? ParentAccountId { get; set; }

    /// <summary>System accounts cannot be deleted (seeded defaults).</summary>
    public bool IsSystem { get; set; } = false;
    public bool IsActive { get; set; } = true;

    /// <summary>Starting balance when the account was first set up in this system.</summary>
    public decimal OpeningBalance { get; set; } = 0;
    /// <summary>The date the opening balance is as-of. Null means it pre-dates all transactions.</summary>
    public DateTime? OpeningBalanceDate { get; set; }

    /// <summary>
    /// Denormalized running balance kept in sync with each transaction. Positive = normal-balance direction
    /// (debit-normal for Assets/Expenses/COGS; credit-normal for Liabilities/Equity/Revenue).
    /// Use AccountsController.RecalculateBalances to rebuild from scratch if needed.
    /// </summary>
    public decimal CurrentBalance { get; set; } = 0;

    // Navigation
    public virtual Account? ParentAccount { get; set; }
    public virtual ICollection<Account> SubAccounts { get; set; } = new List<Account>();
    public virtual ICollection<BillLineItem> BillLineItems { get; set; } = new List<BillLineItem>();
    public virtual ICollection<Bill> Bills { get; set; } = new List<Bill>();
    public virtual ICollection<BillPayment> BillPayments { get; set; } = new List<BillPayment>();
    public virtual ICollection<Expense> Expenses { get; set; } = new List<Expense>();
    public virtual ICollection<Expense> ExpensePaymentAccounts { get; set; } = new List<Expense>();
}

/// <summary>
/// Vendor bill (accounts payable). Represents money owed to a supplier.
/// </summary>
public class Bill : BaseEntity
{
    public string BillNumber { get; set; } = string.Empty;
    /// <summary>Vendor's own invoice/reference number.</summary>
    public string? VendorInvoiceNumber { get; set; }

    public int VendorId { get; set; }
    /// <summary>Which AP account this bill posts to (default: Accounts Payable 2000).</summary>
    public int APAccountId { get; set; }

    public DateTime BillDate { get; set; } = DateTime.UtcNow;
    public DateTime? DueDate { get; set; }

    public BillStatus Status { get; set; } = BillStatus.Draft;
    public string? Terms { get; set; }
    public string? Memo { get; set; }

    // Financials
    public decimal SubTotal { get; set; }
    public decimal TaxPercent { get; set; }
    public decimal TaxAmount { get; set; }
    public decimal Total { get; set; }
    public decimal AmountPaid { get; set; }
    [System.ComponentModel.DataAnnotations.Schema.NotMapped]
    public decimal BalanceDue => Total - AmountPaid;

    /// <summary>Blob path to an attached receipt/invoice document (PDF or image).</summary>
    public string? ReceiptFilePath { get; set; }

    // Navigation
    public virtual Vendor Vendor { get; set; } = null!;
    public virtual Account APAccount { get; set; } = null!;
    public virtual ICollection<BillLineItem> LineItems { get; set; } = new List<BillLineItem>();
    public virtual ICollection<BillPayment> Payments { get; set; } = new List<BillPayment>();
}

/// <summary>
/// A single line on a vendor bill, posted to an expense or asset account.
/// </summary>
public class BillLineItem : BaseEntity
{
    public int BillId { get; set; }
    /// <summary>Expense/asset account this line item is categorized under. Nullable for QB-imported bills where account is unknown.</summary>
    public int? AccountId { get; set; }
    /// <summary>Optional job costing link.</summary>
    public int? JobId { get; set; }

    public string Description { get; set; } = string.Empty;
    public decimal Quantity { get; set; } = 1;
    public decimal UnitPrice { get; set; }
    public decimal Amount { get; set; }
    public int DisplayOrder { get; set; }

    // Navigation
    public virtual Bill Bill { get; set; } = null!;
    public virtual Account? Account { get; set; }
    public virtual Job? Job { get; set; }
}

/// <summary>
/// A payment made against a vendor bill.
/// </summary>
public class BillPayment : BaseEntity
{
    public string PaymentNumber { get; set; } = string.Empty;
    public int BillId { get; set; }
    /// <summary>Denormalized for AP reporting without joining through Bill.</summary>
    public int VendorId { get; set; }
    /// <summary>Bank/cash account the payment came out of.</summary>
    public int BankAccountId { get; set; }

    public DateTime PaymentDate { get; set; } = DateTime.UtcNow;
    public decimal Amount { get; set; }
    public PaymentMethod PaymentMethod { get; set; }
    public string? CheckNumber { get; set; }
    public string? Memo { get; set; }

    /// <summary>True once this payment has been matched against a bank statement during reconciliation.</summary>
    public bool IsCleared { get; set; } = false;
    public DateTime? ClearedDate { get; set; }

    // Navigation
    public virtual Bill Bill { get; set; } = null!;
    public virtual Vendor Vendor { get; set; } = null!;
    public virtual Account BankAccount { get; set; } = null!;
}

/// <summary>
/// A direct expense paid immediately (not via a bill). Covers cash/card purchases.
/// </summary>
public class Expense : BaseEntity
{
    public string ExpenseNumber { get; set; } = string.Empty;
    public DateTime Date { get; set; } = DateTime.UtcNow;

    /// <summary>Optional vendor the expense was paid to.</summary>
    public int? VendorId { get; set; }
    /// <summary>Expense category account (e.g. 6200 Powder & Materials).</summary>
    public int ExpenseAccountId { get; set; }
    /// <summary>Account money came out of (e.g. 1000 Checking, 2100 Credit Card).</summary>
    public int PaymentAccountId { get; set; }
    /// <summary>Optional job costing link.</summary>
    public int? JobId { get; set; }

    public PaymentMethod PaymentMethod { get; set; }
    public decimal Amount { get; set; }
    public string? Memo { get; set; }
    public string? ReceiptFilePath { get; set; }

    /// <summary>True once this expense has been matched against a bank statement during reconciliation.</summary>
    public bool IsCleared { get; set; } = false;
    public DateTime? ClearedDate { get; set; }

    // Navigation
    public virtual Vendor? Vendor { get; set; }
    public virtual Account ExpenseAccount { get; set; } = null!;
    public virtual Account PaymentAccount { get; set; } = null!;
    public virtual Job? Job { get; set; }
}

/// <summary>
/// Manual double-entry journal entry. Lines must balance (sum of debits == sum of credits)
/// before posting. Once posted the entry is immutable — use Reverse to correct it.
/// Entry numbering follows the pattern JE-YYMM-#### scoped per company.
/// </summary>
public class JournalEntry : BaseEntity
{
    public string EntryNumber { get; set; } = string.Empty;
    public DateTime EntryDate { get; set; } = DateTime.UtcNow;
    public string? Reference { get; set; }
    public string? Description { get; set; }
    public JournalEntryStatus Status { get; set; } = JournalEntryStatus.Draft;

    /// <summary>True if this entry was machine-generated as a reversal of another entry.</summary>
    public bool IsReversal { get; set; } = false;
    /// <summary>FK to the original entry being reversed. Null for normal entries.</summary>
    public int? ReversalOfId { get; set; }

    public DateTime? PostedAt { get; set; }
    public string? PostedBy { get; set; }

    // Navigation
    public virtual ICollection<JournalEntryLine> Lines { get; set; } = new List<JournalEntryLine>();
    public virtual JournalEntry? ReversalOf { get; set; }
}

/// <summary>
/// One debit or credit line within a <see cref="JournalEntry"/>. Either DebitAmount or CreditAmount
/// should be non-zero per line (not both). LineOrder controls display sequence.
/// </summary>
public class JournalEntryLine : BaseEntity
{
    public int JournalEntryId { get; set; }
    public int AccountId { get; set; }
    public decimal DebitAmount { get; set; }
    public decimal CreditAmount { get; set; }
    public string? Description { get; set; }
    public int LineOrder { get; set; }

    // Navigation
    public virtual JournalEntry JournalEntry { get; set; } = null!;
    public virtual Account Account { get; set; } = null!;
}

/// <summary>
/// A bank reconciliation session for a single bank/cash account against a statement.
/// Cleared balance = BeginningBalance + cleared deposits - cleared payments.
/// The reconciliation is complete when Difference (EndingBalance - ClearedBalance) == 0.
/// </summary>
public class BankReconciliation : BaseEntity
{
    /// <summary>Must be a bank/cash subtype account.</summary>
    public int AccountId { get; set; }
    public DateTime StatementDate { get; set; }
    public decimal BeginningBalance { get; set; }
    public decimal EndingBalance { get; set; }
    public BankReconciliationStatus Status { get; set; } = BankReconciliationStatus.InProgress;
    public DateTime? CompletedAt { get; set; }
    public string? CompletedBy { get; set; }
    public string? Notes { get; set; }

    // Navigation
    public virtual Account Account { get; set; } = null!;
}

/// <summary>
/// A credit note received from a vendor (returned goods, pricing dispute, short-ship).
/// Reduces Accounts Payable and reverses the original expense/COGS when posted.
/// Numbering: VC-YYMM-####
/// </summary>
public class VendorCredit : BaseEntity
{
    public string CreditNumber { get; set; } = string.Empty;
    public int VendorId { get; set; }
    /// <summary>AP account this credit reduces (default: Accounts Payable 2000).</summary>
    public int APAccountId { get; set; }
    public DateTime CreditDate { get; set; } = DateTime.UtcNow;
    public VendorCreditStatus Status { get; set; } = VendorCreditStatus.Open;
    public decimal Total { get; set; }
    public decimal RemainingAmount { get; set; }
    public string? Memo { get; set; }
    /// <summary>Set by Post() when GL entries are made (DR AP / CR expense lines). Null = unposted.</summary>
    public DateTime? PostedDate { get; set; }

    // Navigation
    public virtual Vendor Vendor { get; set; } = null!;
    public virtual Account APAccount { get; set; } = null!;
    public virtual ICollection<VendorCreditLineItem> LineItems { get; set; } = new List<VendorCreditLineItem>();
    public virtual ICollection<VendorCreditApplication> Applications { get; set; } = new List<VendorCreditApplication>();
}

/// <summary>
/// A single line on a vendor credit, each reversing a specific expense/COGS account.
/// </summary>
public class VendorCreditLineItem : BaseEntity
{
    public int VendorCreditId { get; set; }
    /// <summary>Expense/COGS account being reversed by this line.</summary>
    public int? AccountId { get; set; }
    public string Description { get; set; } = string.Empty;
    public decimal Amount { get; set; }

    // Navigation
    public virtual VendorCredit VendorCredit { get; set; } = null!;
    public virtual Account? Account { get; set; }
}

/// <summary>
/// Records the application of a vendor credit against a specific vendor bill.
/// No additional GL posting is needed — AP was already adjusted when the credit was posted.
/// </summary>
public class VendorCreditApplication : BaseEntity
{
    public int VendorCreditId { get; set; }
    public int BillId { get; set; }
    public decimal Amount { get; set; }
    public DateTime AppliedDate { get; set; } = DateTime.UtcNow;

    // Navigation
    public virtual VendorCredit VendorCredit { get; set; } = null!;
    public virtual Bill Bill { get; set; } = null!;
}

/// <summary>
/// A saved recipe for a document that should be automatically created on a recurring schedule.
/// The <see cref="TemplateData"/> column stores a JSON blob whose schema depends on
/// <see cref="TemplateType"/>: see <c>RecurringTransactionService</c> for the exact shape.
/// <para>
/// Bills are created as Draft so the user can review before posting.
/// Expenses are created immediately (already-paid transactions).
/// </para>
/// Numbering: REC-YYMM-####
/// </summary>
public class RecurringTemplate : BaseEntity
{
    public string Name { get; set; } = string.Empty;
    public RecurringTemplateType TemplateType { get; set; }
    public RecurringFrequency Frequency { get; set; }
    /// <summary>Every N periods. E.g. Frequency=Monthly, IntervalCount=3 → quarterly.</summary>
    public int IntervalCount { get; set; } = 1;
    /// <summary>UTC date when the template will next fire. Set to the desired first occurrence date on creation.</summary>
    public DateTime NextFireDate { get; set; }
    /// <summary>Optional UTC date after which no further occurrences are generated.</summary>
    public DateTime? EndDate { get; set; }
    /// <summary>Optional hard cap on total occurrences. Null = unlimited.</summary>
    public int? MaxOccurrences { get; set; }
    /// <summary>How many documents have been generated so far.</summary>
    public int OccurrenceCount { get; set; }
    public bool IsActive { get; set; } = true;
    /// <summary>JSON payload whose schema matches the TemplateType. See RecurringTransactionService.</summary>
    public string TemplateData { get; set; } = "{}";
    /// <summary>Last error from the background service, cleared on next successful fire.</summary>
    public string? LastError { get; set; }
}

/// <summary>
/// A named tax rate (e.g., "CA Sales Tax 8.25%") used to pre-fill the TaxPercent field on
/// invoices when a taxable customer is selected. Companies can define multiple rates for
/// different jurisdictions and mark one as default.
/// </summary>
public class TaxRate : BaseEntity
{
    public string Name { get; set; } = string.Empty;
    /// <summary>Rate as a percentage, e.g., 8.25 means 8.25%.</summary>
    public decimal Rate { get; set; }
    public string? State { get; set; }
    public string? Description { get; set; }
    /// <summary>When true, this rate is auto-applied to new invoices for taxable customers.</summary>
    public bool IsDefault { get; set; }
    public bool IsActive { get; set; } = true;
}

/// <summary>
/// A depreciable fixed asset (oven, blast cabinet, spray booth, vehicle, etc.).
/// Stores straight-line depreciation parameters and links to the three GL accounts needed
/// to auto-post monthly depreciation journal entries.
/// </summary>
public class FixedAsset : BaseEntity
{
    public string Name { get; set; } = string.Empty;
    public string? Description { get; set; }
    public DateTime PurchaseDate { get; set; }
    public decimal PurchaseCost { get; set; }
    /// <summary>Residual value at end of useful life (often $0 for shop equipment).</summary>
    public decimal SalvageValue { get; set; } = 0;
    /// <summary>Total depreciation period in months (e.g., 60 = 5 years).</summary>
    public int UsefulLifeMonths { get; set; }
    /// <summary>Running total of depreciation posted so far.</summary>
    public decimal AccumulatedDepreciation { get; set; } = 0;
    public bool IsDisposed { get; set; } = false;
    public DateTime? DisposalDate { get; set; }

    // Computed — not persisted
    /// <summary>Current net book value: PurchaseCost minus AccumulatedDepreciation.</summary>
    public decimal BookValue => PurchaseCost - AccumulatedDepreciation;
    /// <summary>Straight-line monthly depreciation amount.</summary>
    public decimal MonthlyDepreciation => UsefulLifeMonths > 0
        ? Math.Round((PurchaseCost - SalvageValue) / UsefulLifeMonths, 2) : 0;

    // GL account links — all optional; assets without accounts can be tracked but not auto-posted
    /// <summary>Balance Sheet FixedAsset account (debited when asset is purchased).</summary>
    public int? AssetAccountId { get; set; }
    /// <summary>P&L Depreciation Expense account (debited each period).</summary>
    public int? DepreciationExpenseAccountId { get; set; }
    /// <summary>Balance Sheet Accumulated Depreciation account (credited each period).</summary>
    public int? AccumDepreciationAccountId { get; set; }

    // Navigation
    public virtual Account? AssetAccount { get; set; }
    public virtual Account? DepreciationExpenseAccount { get; set; }
    public virtual Account? AccumDepreciationAccount { get; set; }
    public virtual ICollection<FixedAssetDepreciationEntry> DepreciationEntries { get; set; } = new List<FixedAssetDepreciationEntry>();
}

/// <summary>
/// Records each periodic depreciation posting for a fixed asset. One record per asset per
/// month/year combination; linked to the JournalEntry that was created so the posting
/// can be traced back through the GL.
/// </summary>
public class FixedAssetDepreciationEntry : BaseEntity
{
    public int FixedAssetId { get; set; }
    public int PeriodYear { get; set; }
    public int PeriodMonth { get; set; }
    public decimal Amount { get; set; }
    /// <summary>The JE that was posted for this depreciation period (null if manually recorded).</summary>
    public int? JournalEntryId { get; set; }

    // Navigation
    public virtual FixedAsset FixedAsset { get; set; } = null!;
    public virtual JournalEntry? JournalEntry { get; set; }
}

/// <summary>
/// A named annual budget. Contains one BudgetLine per account per month. Supports
/// multiple budgets per fiscal year (e.g. "Conservative" vs "Optimistic") but only
/// one is marked IsDefault for the Budget vs. Actual report.
/// </summary>
public class Budget : BaseEntity
{
    public string Name { get; set; } = string.Empty;
    public int FiscalYear { get; set; }
    public string? Notes { get; set; }
    public bool IsDefault { get; set; } = false;

    public virtual ICollection<BudgetLine> Lines { get; set; } = new List<BudgetLine>();
}

/// <summary>
/// Monthly budget amount for one account within a Budget. Jan–Dec stored as separate
/// columns so the grid editor can write them in a single POST without a line-item loop.
/// Annual is a computed property summing all twelve months.
/// </summary>
public class BudgetLine : BaseEntity
{
    public int BudgetId { get; set; }
    public int AccountId { 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 virtual Budget Budget { get; set; } = null!;
    public virtual Account Account { get; set; } = null!;
}

/// <summary>
/// Records a completed year-end close. The close posts a JE that zeroes all
/// Revenue and Expense account balances into Retained Earnings, and marks
/// the year as closed so it cannot be closed again.
/// </summary>
public class YearEndClose : BaseEntity
{
    public int ClosedYear { get; set; }
    public DateTime ClosedAt { get; set; } = DateTime.UtcNow;
    public string? ClosedBy { get; set; }
    public int JournalEntryId { get; set; }

    public virtual JournalEntry JournalEntry { get; set; } = null!;
}
