"""The Books — bookkeeping, management accounts, and tax prep as features.

These mirror services a firm would normally sell by hand (Bookkeeping,
Management Accounts, Financial Statement Preparation, Tax/VAT Filing). Every
figure is aggregated from the company's own invoices, payments, and expenses
for a chosen period.
"""

from calendar import monthrange
from datetime import date
from decimal import Decimal

from django.db.models import Sum

from apps.expenses.models import Expense
from apps.invoicing.models import Invoice
from apps.payments.models import Payment

PERIODS = ('this_month', 'last_month', 'this_quarter', 'this_year')


def resolve_period(period: str, today: date | None = None) -> tuple[date, date, str]:
    """Return (start, end, label) for a named period."""
    today = today or date.today()

    if period == 'last_month':
        year, month = (today.year - 1, 12) if today.month == 1 else (today.year, today.month - 1)
        start = date(year, month, 1)
        end = date(year, month, monthrange(year, month)[1])
        return start, end, start.strftime('%B %Y')

    if period == 'this_quarter':
        quarter_start_month = 3 * ((today.month - 1) // 3) + 1
        start = date(today.year, quarter_start_month, 1)
        return start, today, f'Q{(today.month - 1) // 3 + 1} {today.year}'

    if period == 'this_year':
        start = date(today.year, 1, 1)
        return start, today, str(today.year)

    # Default: this month.
    start = date(today.year, today.month, 1)
    return start, today, start.strftime('%B %Y')


def compute_books(company, period: str = 'this_month') -> dict:
    start, end, label = resolve_period(period)

    # Drafts are not issued and cancelled invoices are void — neither belongs
    # in a set of books.
    invoices = Invoice.objects.filter(
        company=company, issue_date__gte=start, issue_date__lte=end
    ).exclude(status__in=[Invoice.Status.CANCELLED, Invoice.Status.DRAFT])

    invoice_totals = invoices.aggregate(invoiced=Sum('total'), tax=Sum('tax_amount'), net=Sum('subtotal'))
    invoiced = invoice_totals['invoiced'] or Decimal('0')
    tax_collected = invoice_totals['tax'] or Decimal('0')
    taxable_sales = invoice_totals['net'] or Decimal('0')

    payments = Payment.objects.filter(
        invoice__company=company, payment_date__gte=start, payment_date__lte=end
    ).select_related('invoice', 'invoice__customer')
    collected = payments.aggregate(t=Sum('amount'))['t'] or Decimal('0')

    expense_qs = Expense.objects.filter(
        company=company, expense_date__gte=start, expense_date__lte=end
    ).select_related('category')
    expenses_total = expense_qs.aggregate(t=Sum('amount'))['t'] or Decimal('0')

    breakdown = [
        {'category': row['category__name'] or 'Uncategorized', 'total': row['total']}
        for row in expense_qs.values('category__name').annotate(total=Sum('amount')).order_by('-total')
    ]

    net_profit = invoiced - expenses_total
    margin = float(net_profit / invoiced * 100) if invoiced > 0 else 0.0

    # Ledger: real cash movements, oldest first, with a running balance.
    entries = []
    for payment in payments:
        customer = payment.invoice.customer.name if payment.invoice and payment.invoice.customer else ''
        entries.append({
            'date': payment.payment_date,
            'kind': 'in',
            'description': f'Payment received{f" from {customer}" if customer else ""}',
            'reference': payment.invoice.invoice_number if payment.invoice else '',
            'amount': payment.amount,
        })
    for expense in expense_qs:
        entries.append({
            'date': expense.expense_date,
            'kind': 'out',
            'description': expense.description or (expense.category.name if expense.category else 'Expense'),
            'reference': expense.category.name if expense.category else '',
            'amount': expense.amount,
        })

    entries.sort(key=lambda e: e['date'])
    balance = Decimal('0')
    ledger = []
    for entry in entries:
        balance += entry['amount'] if entry['kind'] == 'in' else -entry['amount']
        ledger.append({**entry, 'date': entry['date'].isoformat(), 'balance': balance})

    return {
        'period': {'key': period, 'label': label, 'start': start.isoformat(), 'end': end.isoformat()},
        'income_statement': {
            'invoiced': invoiced,
            'collected': collected,
            'expenses': expenses_total,
            'net_profit': net_profit,
            'margin': round(margin, 1),
            'expense_breakdown': breakdown,
        },
        'tax': {
            'tax_collected': tax_collected,
            'taxable_sales': taxable_sales,
            'invoice_count': invoices.count(),
        },
        'ledger': ledger,
        'net_movement': balance,
    }
