"""
Validation rules for Castle accounting.

Comprehensive validation following Beancount's plugin system approach,
but implemented as simple functions that can be called directly.
"""

from decimal import Decimal
from typing import Any, Dict, List, Optional


class ValidationError(Exception):
    """Raised when validation fails"""

    def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
        super().__init__(message)
        self.message = message
        self.details = details or {}


def validate_journal_entry(
    entry: Dict[str, Any],
    entry_lines: List[Dict[str, Any]]
) -> None:
    """
    Validate a journal entry and its lines.

    Checks:
    1. Entry must have at least 2 lines (double-entry requirement)
    2. Entry must be balanced (sum of debits = sum of credits)
    3. All lines must have valid amounts (non-negative)
    4. All lines must have account_id

    Args:
        entry: Journal entry dict with keys:
            - id: str
            - description: str
            - entry_date: datetime
        entry_lines: List of entry line dicts with keys:
            - account_id: str
            - debit: int
            - credit: int

    Raises:
        ValidationError: If validation fails
    """
    # Check minimum number of lines
    if len(entry_lines) < 2:
        raise ValidationError(
            "Journal entry must have at least 2 lines",
            {
                "entry_id": entry.get("id"),
                "line_count": len(entry_lines),
            }
        )

    # Validate each line
    for i, line in enumerate(entry_lines):
        # Check account_id exists
        if not line.get("account_id"):
            raise ValidationError(
                f"Entry line {i + 1} missing account_id",
                {
                    "entry_id": entry.get("id"),
                    "line_index": i,
                }
            )

        # Check amounts are non-negative
        debit = line.get("debit", 0)
        credit = line.get("credit", 0)

        if debit < 0:
            raise ValidationError(
                f"Entry line {i + 1} has negative debit: {debit}",
                {
                    "entry_id": entry.get("id"),
                    "line_index": i,
                    "debit": debit,
                }
            )

        if credit < 0:
            raise ValidationError(
                f"Entry line {i + 1} has negative credit: {credit}",
                {
                    "entry_id": entry.get("id"),
                    "line_index": i,
                    "credit": credit,
                }
            )

        # Check that a line doesn't have both debit and credit
        if debit > 0 and credit > 0:
            raise ValidationError(
                f"Entry line {i + 1} has both debit and credit",
                {
                    "entry_id": entry.get("id"),
                    "line_index": i,
                    "debit": debit,
                    "credit": credit,
                }
            )

        # Check that a line has at least one non-zero amount
        if debit == 0 and credit == 0:
            raise ValidationError(
                f"Entry line {i + 1} has both debit and credit as zero",
                {
                    "entry_id": entry.get("id"),
                    "line_index": i,
                }
            )

    # Check entry is balanced
    total_debits = sum(line.get("debit", 0) for line in entry_lines)
    total_credits = sum(line.get("credit", 0) for line in entry_lines)

    if total_debits != total_credits:
        raise ValidationError(
            "Journal entry is not balanced",
            {
                "entry_id": entry.get("id"),
                "total_debits": total_debits,
                "total_credits": total_credits,
                "difference": total_debits - total_credits,
            }
        )


def validate_balance(
    account_id: str,
    expected_balance_sats: int,
    actual_balance_sats: int,
    tolerance_sats: int = 0,
    expected_balance_fiat: Optional[Decimal] = None,
    actual_balance_fiat: Optional[Decimal] = None,
    tolerance_fiat: Optional[Decimal] = None,
    fiat_currency: Optional[str] = None
) -> None:
    """
    Validate that actual balance matches expected balance within tolerance.

    Args:
        account_id: Account being checked
        expected_balance_sats: Expected satoshi balance
        actual_balance_sats: Actual calculated satoshi balance
        tolerance_sats: Allowed difference for sats (±)
        expected_balance_fiat: Expected fiat balance (optional)
        actual_balance_fiat: Actual fiat balance (optional)
        tolerance_fiat: Allowed difference for fiat (±)
        fiat_currency: Fiat currency code

    Raises:
        ValidationError: If balance doesn't match
    """
    # Check sats balance
    sats_difference = actual_balance_sats - expected_balance_sats
    if abs(sats_difference) > tolerance_sats:
        raise ValidationError(
            f"Balance assertion failed for account {account_id}",
            {
                "account_id": account_id,
                "expected_sats": expected_balance_sats,
                "actual_sats": actual_balance_sats,
                "difference_sats": sats_difference,
                "tolerance_sats": tolerance_sats,
            }
        )

    # Check fiat balance if provided
    if expected_balance_fiat is not None and actual_balance_fiat is not None:
        if tolerance_fiat is None:
            tolerance_fiat = Decimal(0)

        fiat_difference = actual_balance_fiat - expected_balance_fiat
        if abs(fiat_difference) > tolerance_fiat:
            raise ValidationError(
                f"Fiat balance assertion failed for account {account_id}",
                {
                    "account_id": account_id,
                    "currency": fiat_currency,
                    "expected_fiat": float(expected_balance_fiat),
                    "actual_fiat": float(actual_balance_fiat),
                    "difference_fiat": float(fiat_difference),
                    "tolerance_fiat": float(tolerance_fiat),
                }
            )


def validate_receivable_entry(
    user_id: str,
    amount: int,
    revenue_account_type: str
) -> None:
    """
    Validate a receivable entry (user owes castle).

    Args:
        user_id: User ID
        amount: Amount in sats (must be positive)
        revenue_account_type: Must be "revenue"

    Raises:
        ValidationError: If validation fails
    """
    if amount <= 0:
        raise ValidationError(
            "Receivable amount must be positive",
            {"user_id": user_id, "amount": amount}
        )

    if revenue_account_type != "revenue":
        raise ValidationError(
            "Receivable must credit a revenue account",
            {
                "user_id": user_id,
                "provided_account_type": revenue_account_type,
            }
        )


def validate_expense_entry(
    user_id: str,
    amount: int,
    expense_account_type: str,
    is_equity: bool
) -> None:
    """
    Validate an expense entry (user spent money).

    Args:
        user_id: User ID
        amount: Amount in sats (must be positive)
        expense_account_type: Must be "expense" (unless is_equity is True)
        is_equity: If True, this is an equity contribution

    Raises:
        ValidationError: If validation fails
    """
    if amount <= 0:
        raise ValidationError(
            "Expense amount must be positive",
            {"user_id": user_id, "amount": amount}
        )

    if not is_equity and expense_account_type != "expense":
        raise ValidationError(
            "Expense must debit an expense account",
            {
                "user_id": user_id,
                "provided_account_type": expense_account_type,
            }
        )


def validate_payment_entry(
    user_id: str,
    amount: int
) -> None:
    """
    Validate a payment entry (user paid their debt).

    Args:
        user_id: User ID
        amount: Amount in sats (must be positive)

    Raises:
        ValidationError: If validation fails
    """
    if amount <= 0:
        raise ValidationError(
            "Payment amount must be positive",
            {"user_id": user_id, "amount": amount}
        )


def validate_metadata(
    metadata: Dict[str, Any],
    required_keys: Optional[List[str]] = None
) -> None:
    """
    Validate entry line metadata.

    Args:
        metadata: Metadata dictionary
        required_keys: List of required keys

    Raises:
        ValidationError: If validation fails
    """
    if required_keys:
        missing_keys = [key for key in required_keys if key not in metadata]
        if missing_keys:
            raise ValidationError(
                f"Metadata missing required keys: {', '.join(missing_keys)}",
                {
                    "missing_keys": missing_keys,
                    "provided_keys": list(metadata.keys()),
                }
            )

    # Validate fiat currency and amount consistency
    has_fiat_currency = "fiat_currency" in metadata
    has_fiat_amount = "fiat_amount" in metadata

    if has_fiat_currency != has_fiat_amount:
        raise ValidationError(
            "fiat_currency and fiat_amount must both be present or both absent",
            {
                "has_fiat_currency": has_fiat_currency,
                "has_fiat_amount": has_fiat_amount,
            }
        )

    # Validate fiat amount is valid Decimal
    if has_fiat_amount:
        try:
            Decimal(str(metadata["fiat_amount"]))
        except (ValueError, TypeError) as e:
            raise ValidationError(
                f"Invalid fiat_amount: {metadata['fiat_amount']}",
                {"error": str(e)}
            )
