from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Optional

from pydantic import BaseModel, Field


class AccountType(str, Enum):
    ASSET = "asset"
    LIABILITY = "liability"
    EQUITY = "equity"
    REVENUE = "revenue"
    EXPENSE = "expense"


class JournalEntryFlag(str, Enum):
    """Transaction status flags (Beancount-style)"""
    CLEARED = "*"      # Fully reconciled/confirmed
    PENDING = "!"      # Not yet confirmed/awaiting approval
    FLAGGED = "#"      # Needs review/attention
    VOID = "x"         # Voided/cancelled entry


class Account(BaseModel):
    id: str
    name: str
    account_type: AccountType
    description: Optional[str] = None
    user_id: Optional[str] = None  # For user-specific accounts
    created_at: datetime


class CreateAccount(BaseModel):
    name: str
    account_type: AccountType
    description: Optional[str] = None
    user_id: Optional[str] = None


class EntryLine(BaseModel):
    id: str
    journal_entry_id: str
    account_id: str
    debit: int = 0  # in satoshis
    credit: int = 0  # in satoshis
    description: Optional[str] = None
    metadata: dict = {}  # Stores currency info: fiat_currency, fiat_amount, fiat_rate, etc.


class CreateEntryLine(BaseModel):
    account_id: str
    debit: int = 0
    credit: int = 0
    description: Optional[str] = None
    metadata: dict = {}  # Stores currency info


class JournalEntry(BaseModel):
    id: str
    description: str
    entry_date: datetime
    created_by: str  # wallet ID of user who created it
    created_at: datetime
    reference: Optional[str] = None  # Invoice ID or reference number
    lines: list[EntryLine] = []
    flag: JournalEntryFlag = JournalEntryFlag.CLEARED  # Transaction status
    meta: dict = {}  # Metadata: source, tags, links, notes, etc.


class CreateJournalEntry(BaseModel):
    description: str
    entry_date: Optional[datetime] = None
    reference: Optional[str] = None
    lines: list[CreateEntryLine]
    flag: JournalEntryFlag = JournalEntryFlag.CLEARED
    meta: dict = {}


class UserBalance(BaseModel):
    user_id: str
    balance: int  # positive = castle owes user, negative = user owes castle
    accounts: list[Account] = []
    fiat_balances: dict[str, Decimal] = {}  # e.g. {"EUR": Decimal("250.0"), "USD": Decimal("100.0")}


class ExpenseEntry(BaseModel):
    """Helper model for creating expense entries"""

    description: str
    amount: Decimal  # Amount in the specified currency (or satoshis if currency is None)
    expense_account: str  # account name or ID
    is_equity: bool = False  # True = equity contribution, False = liability (castle owes user)
    user_wallet: str
    reference: Optional[str] = None
    currency: Optional[str] = None  # If None, amount is in satoshis. Otherwise, fiat currency code (EUR, USD, etc.)
    entry_date: Optional[datetime] = None  # Date of the expense transaction


class ReceivableEntry(BaseModel):
    """Helper model for creating accounts receivable entries"""

    description: str
    amount: Decimal  # Amount in the specified currency (or satoshis if currency is None)
    revenue_account: str  # account name or ID
    user_id: str  # The user_id (not wallet_id) of the user who owes the castle
    reference: Optional[str] = None
    currency: Optional[str] = None  # If None, amount is in satoshis. Otherwise, fiat currency code


class RevenueEntry(BaseModel):
    """Helper model for creating revenue entries"""

    description: str
    amount: Decimal  # Amount in the specified currency (or satoshis if currency is None)
    revenue_account: str
    payment_method_account: str  # e.g., "Cash", "Bank", "Lightning"
    reference: Optional[str] = None
    currency: Optional[str] = None  # If None, amount is in satoshis. Otherwise, fiat currency code


class CastleSettings(BaseModel):
    """Settings for the Castle extension"""

    castle_wallet_id: Optional[str] = None  # The wallet ID that represents the Castle
    updated_at: datetime = Field(default_factory=lambda: datetime.now())

    @classmethod
    def is_admin_only(cls) -> bool:
        return True


class UserCastleSettings(CastleSettings):
    """User-specific settings (stored with user_id)"""

    id: str


class UserWalletSettings(BaseModel):
    """Per-user wallet settings"""

    user_wallet_id: Optional[str] = None  # The wallet ID for this specific user
    updated_at: datetime = Field(default_factory=lambda: datetime.now())


class StoredUserWalletSettings(UserWalletSettings):
    """Stored user wallet settings with user ID"""

    id: str  # user_id


class ManualPaymentRequest(BaseModel):
    """Manual payment request from user to castle"""

    id: str
    user_id: str
    amount: int  # in satoshis
    description: str
    status: str = "pending"  # pending, approved, rejected
    created_at: datetime
    reviewed_at: Optional[datetime] = None
    reviewed_by: Optional[str] = None  # user_id of castle admin who reviewed
    journal_entry_id: Optional[str] = None  # set when approved


class CreateManualPaymentRequest(BaseModel):
    """Create a manual payment request"""

    amount: int
    description: str


class GeneratePaymentInvoice(BaseModel):
    """Generate payment invoice request"""

    amount: int
    user_id: Optional[str] = None  # For admin-generated settlement invoices


class RecordPayment(BaseModel):
    """Record a payment"""

    payment_hash: str


class SettleReceivable(BaseModel):
    """Manually settle a receivable (user pays castle in person)"""

    user_id: str
    amount: Decimal  # Amount in the specified currency (or satoshis if currency is None)
    payment_method: str  # "cash", "bank_transfer", "check", "lightning", "btc_onchain", "other"
    description: str  # Description of the payment
    reference: Optional[str] = None  # Optional reference (receipt number, transaction ID, etc.)
    currency: Optional[str] = None  # If None, amount is in satoshis. Otherwise, fiat currency code (EUR, USD, etc.)
    amount_sats: Optional[int] = None  # Equivalent amount in sats (for reference/conversion tracking)
    payment_hash: Optional[str] = None  # For lightning payments
    txid: Optional[str] = None  # For on-chain Bitcoin transactions


class PayUser(BaseModel):
    """Pay a user (castle pays user for expense/liability)"""

    user_id: str
    amount: Decimal  # Amount in the specified currency (or satoshis if currency is None)
    payment_method: str  # "cash", "bank_transfer", "check", "lightning", "btc_onchain", "other"
    description: Optional[str] = None  # Description of the payment
    reference: Optional[str] = None  # Optional reference (receipt number, transaction ID, etc.)
    currency: Optional[str] = None  # If None, amount is in satoshis. Otherwise, fiat currency code (EUR, USD, etc.)
    amount_sats: Optional[int] = None  # Equivalent amount in sats (for reference/conversion tracking)
    payment_hash: Optional[str] = None  # For lightning payments
    txid: Optional[str] = None  # For on-chain Bitcoin transactions


class AssertionStatus(str, Enum):
    """Status of a balance assertion"""
    PENDING = "pending"    # Not yet checked
    PASSED = "passed"      # Assertion passed (balance matches)
    FAILED = "failed"      # Assertion failed (balance mismatch)


class BalanceAssertion(BaseModel):
    """Assert expected balance at a specific date for reconciliation"""
    id: str
    date: datetime
    account_id: str
    expected_balance_sats: int  # Expected balance in satoshis
    expected_balance_fiat: Optional[Decimal] = None  # Optional fiat balance
    fiat_currency: Optional[str] = None  # Currency for fiat balance (EUR, USD, etc.)
    tolerance_sats: int = 0  # Allow +/- this much difference in sats
    tolerance_fiat: Decimal = Decimal("0")  # Allow +/- this much difference in fiat
    checked_balance_sats: Optional[int] = None  # Actual balance found
    checked_balance_fiat: Optional[Decimal] = None  # Actual fiat balance found
    difference_sats: Optional[int] = None  # Difference in sats
    difference_fiat: Optional[Decimal] = None  # Difference in fiat
    status: AssertionStatus = AssertionStatus.PENDING
    created_by: str
    created_at: datetime
    checked_at: Optional[datetime] = None


class CreateBalanceAssertion(BaseModel):
    """Create a balance assertion"""
    account_id: str
    date: Optional[datetime] = None  # If None, use current time
    expected_balance_sats: int
    expected_balance_fiat: Optional[Decimal] = None
    fiat_currency: Optional[str] = None
    tolerance_sats: int = 0
    tolerance_fiat: Decimal = Decimal("0")
