# /home/Autopartes/pos/blueprints/accounting_bp.py
"""Accounting blueprint: chart of accounts, journal entries, financial reports.

Financial reports (balance sheet, income statement, trial balance, aging)
are computed via SQL aggregation on journal_entry_lines. All amounts are
NUMERIC(14,2) in the database.
"""

import json
import csv
import io
from datetime import date, datetime, timedelta
from flask import Blueprint, request, jsonify, g, Response
from middleware import require_auth
from tenant_db import get_tenant_conn
from services.accounting_engine import create_manual_entry
from services.audit import log_action

accounting_bp = Blueprint('accounting', __name__, url_prefix='/pos/api/accounting')


# ─── Chart of Accounts ─────────────────────────────

@accounting_bp.route('/accounts', methods=['GET'])
@require_auth('accounting.view')
def list_accounts():
    """Get chart of accounts as a flat list (frontend builds tree).

    Returns all active accounts with parent_id for tree construction.
    """
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    cur.execute("""
        SELECT a.id, a.code, a.name, a.parent_id, a.type, a.sat_code,
               a.is_system, a.is_active,
               COALESCE(
                   (SELECT SUM(l.debit) - SUM(l.credit)
                    FROM journal_entry_lines l
                    JOIN journal_entries e ON l.journal_entry_id = e.id
                    WHERE l.account_id = a.id AND e.status = 'posted'), 0
               ) as balance
        FROM accounts a
        WHERE a.is_active = true
        ORDER BY a.code
    """)

    accounts = []
    for r in cur.fetchall():
        accounts.append({
            'id': r[0], 'code': r[1], 'name': r[2],
            'parent_id': r[3], 'type': r[4], 'sat_code': r[5],
            'is_system': r[6], 'is_active': r[7],
            'balance': float(r[8]) if r[8] else 0,
        })

    cur.close()
    conn.close()
    return jsonify({'data': accounts})


@accounting_bp.route('/accounts', methods=['POST'])
@require_auth('accounting.create')
def create_account():
    """Create a new sub-account.

    Body: {
        code: str (must be unique),
        name: str,
        parent_id: int (must exist),
        type: 'activo' | 'pasivo' | 'capital' | 'ingreso' | 'costo' | 'gasto',
        sat_code: str (optional)
    }

    System accounts (is_system=true) cannot be created via API.
    """
    data = request.get_json() or {}
    code = data.get('code', '').strip()
    name = data.get('name', '').strip()
    parent_id = data.get('parent_id')
    acct_type = data.get('type', '')
    sat_code = data.get('sat_code')

    if not code or not name:
        return jsonify({'error': 'code and name are required'}), 400

    valid_types = ('activo', 'pasivo', 'capital', 'ingreso', 'costo', 'gasto')
    if acct_type not in valid_types:
        return jsonify({'error': f'type must be one of: {", ".join(valid_types)}'}), 400

    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    try:
        # Check uniqueness
        cur.execute("SELECT id FROM accounts WHERE code = %s", (code,))
        if cur.fetchone():
            return jsonify({'error': f'Account code {code} already exists'}), 409

        # Validate parent exists
        if parent_id:
            cur.execute("SELECT id FROM accounts WHERE id = %s AND is_active = true", (parent_id,))
            if not cur.fetchone():
                return jsonify({'error': f'Parent account {parent_id} not found'}), 404

        cur.execute("""
            INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active)
            VALUES (%s, %s, %s, %s, %s, false, true)
            RETURNING id
        """, (code, name, parent_id, acct_type, sat_code))
        acct_id = cur.fetchone()[0]

        log_action(conn, 'ACCOUNT_CREATED', 'account', acct_id,
                   new_value={'code': code, 'name': name, 'type': acct_type})

        conn.commit()
        cur.close()
        conn.close()

        return jsonify({'id': acct_id, 'code': code, 'name': name, 'type': acct_type}), 201

    except Exception as e:
        conn.rollback()
        cur.close()
        conn.close()
        return jsonify({'error': str(e)}), 500


# ─── Journal Entries ────────────────────────────────

@accounting_bp.route('/entries', methods=['GET'])
@require_auth('accounting.view')
def list_entries():
    """List journal entries with filters.

    Query params: date_from, date_to, type, reference_type, is_auto, page, per_page
    """
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    page = int(request.args.get('page', 1))
    per_page = min(int(request.args.get('per_page', 50)), 200)

    where_clauses = ["1=1"]
    params = []

    date_from = request.args.get('date_from')
    date_to = request.args.get('date_to')
    entry_type = request.args.get('type')
    ref_type = request.args.get('reference_type')
    is_auto = request.args.get('is_auto')

    if date_from:
        where_clauses.append("je.date >= %s")
        params.append(date_from)
    if date_to:
        where_clauses.append("je.date <= %s")
        params.append(date_to)
    if entry_type:
        where_clauses.append("je.type = %s")
        params.append(entry_type)
    if ref_type:
        where_clauses.append("je.reference_type = %s")
        params.append(ref_type)
    if is_auto is not None:
        where_clauses.append("je.is_auto = %s")
        params.append(is_auto.lower() in ('true', '1'))

    where = " AND ".join(where_clauses)

    cur.execute(f"SELECT count(*) FROM journal_entries je WHERE {where}", params)
    total = cur.fetchone()[0]

    cur.execute(f"""
        SELECT je.id, je.entry_number, je.date, je.type, je.description,
               je.reference_type, je.reference_id, je.status, je.is_auto,
               je.created_at, e.name as created_by_name,
               (SELECT SUM(debit) FROM journal_entry_lines WHERE journal_entry_id = je.id) as total_debit
        FROM journal_entries je
        LEFT JOIN employees e ON je.created_by = e.id
        WHERE {where}
        ORDER BY je.date DESC, je.entry_number DESC
        LIMIT %s OFFSET %s
    """, params + [per_page, (page - 1) * per_page])

    entries = []
    for r in cur.fetchall():
        entries.append({
            'id': r[0], 'entry_number': r[1],
            'date': str(r[2]) if r[2] else None,
            'type': r[3], 'description': r[4],
            'reference_type': r[5], 'reference_id': r[6],
            'status': r[7], 'is_auto': r[8],
            'created_at': str(r[9]) if r[9] else None,
            'created_by_name': r[10],
            'total_amount': float(r[11]) if r[11] else 0,
        })

    cur.close()
    conn.close()

    total_pages = (total + per_page - 1) // per_page
    return jsonify({
        'data': entries,
        'pagination': {'page': page, 'per_page': per_page, 'total': total, 'total_pages': total_pages}
    })


@accounting_bp.route('/entries/<int:entry_id>', methods=['GET'])
@require_auth('accounting.view')
def get_entry(entry_id):
    """Get journal entry detail with all lines."""
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    cur.execute("""
        SELECT je.id, je.entry_number, je.date, je.type, je.description,
               je.reference_type, je.reference_id, je.status, je.is_auto,
               je.created_at, je.created_by, e.name as created_by_name
        FROM journal_entries je
        LEFT JOIN employees e ON je.created_by = e.id
        WHERE je.id = %s
    """, (entry_id,))
    row = cur.fetchone()
    if not row:
        cur.close(); conn.close()
        return jsonify({'error': 'Journal entry not found'}), 404

    entry = {
        'id': row[0], 'entry_number': row[1],
        'date': str(row[2]) if row[2] else None,
        'type': row[3], 'description': row[4],
        'reference_type': row[5], 'reference_id': row[6],
        'status': row[7], 'is_auto': row[8],
        'created_at': str(row[9]) if row[9] else None,
        'created_by': row[10], 'created_by_name': row[11],
    }

    # Get lines
    cur.execute("""
        SELECT l.id, l.account_id, a.code, a.name, l.debit, l.credit, l.description
        FROM journal_entry_lines l
        JOIN accounts a ON l.account_id = a.id
        WHERE l.journal_entry_id = %s
        ORDER BY l.id
    """, (entry_id,))

    entry['lines'] = []
    total_debit = 0
    total_credit = 0
    for r in cur.fetchall():
        debit = float(r[4]) if r[4] else 0
        credit = float(r[5]) if r[5] else 0
        total_debit += debit
        total_credit += credit
        entry['lines'].append({
            'id': r[0], 'account_id': r[1], 'account_code': r[2],
            'account_name': r[3], 'debit': debit, 'credit': credit,
            'description': r[6],
        })

    entry['total_debit'] = round(total_debit, 2)
    entry['total_credit'] = round(total_credit, 2)

    cur.close()
    conn.close()
    return jsonify(entry)


@accounting_bp.route('/entries', methods=['POST'])
@require_auth('accounting.create')
def create_entry():
    """Create a manual journal entry.

    Body: {
        date: 'YYYY-MM-DD',
        description: str,
        lines: [{account_id: int, debit: float, credit: float, description: str}]
    }
    """
    data = request.get_json() or {}

    if not data.get('date'):
        return jsonify({'error': 'date is required'}), 400
    if not data.get('lines') or len(data['lines']) < 2:
        return jsonify({'error': 'At least 2 lines are required'}), 400

    conn = get_tenant_conn(g.tenant_id)

    try:
        entry_id = create_manual_entry(conn, data)

        log_action(conn, 'MANUAL_ENTRY', 'journal_entry', entry_id,
                   new_value={'date': data['date'], 'description': data.get('description', '')})

        conn.commit()
        conn.close()
        return jsonify({'id': entry_id, 'status': 'posted'}), 201

    except ValueError as e:
        conn.rollback()
        conn.close()
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        conn.rollback()
        conn.close()
        return jsonify({'error': str(e)}), 500


# ─── Financial Reports ──────────────────────────────

@accounting_bp.route('/trial-balance', methods=['GET'])
@require_auth('accounting.view')
def trial_balance():
    """Balanza de comprobacion for a period.

    Query params:
        year: int (default current year)
        month: int (default current month)

    Returns each account with: saldo_inicial, cargos, abonos, saldo_final
    """
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    year = int(request.args.get('year', date.today().year))
    month = int(request.args.get('month', date.today().month))

    # Period start/end dates
    period_start = f'{year}-{month:02d}-01'
    if month == 12:
        period_end = f'{year + 1}-01-01'
    else:
        period_end = f'{year}-{month + 1:02d}-01'

    cur.execute("""
        WITH initial_balances AS (
            SELECT l.account_id,
                   SUM(l.debit) as prev_debits,
                   SUM(l.credit) as prev_credits
            FROM journal_entry_lines l
            JOIN journal_entries e ON l.journal_entry_id = e.id
            WHERE e.status = 'posted' AND e.date < %s
            GROUP BY l.account_id
        ),
        period_movements AS (
            SELECT l.account_id,
                   SUM(l.debit) as period_debits,
                   SUM(l.credit) as period_credits
            FROM journal_entry_lines l
            JOIN journal_entries e ON l.journal_entry_id = e.id
            WHERE e.status = 'posted' AND e.date >= %s AND e.date < %s
            GROUP BY l.account_id
        )
        SELECT a.id, a.code, a.name, a.type, a.parent_id,
               COALESCE(ib.prev_debits, 0) as prev_debits,
               COALESCE(ib.prev_credits, 0) as prev_credits,
               COALESCE(pm.period_debits, 0) as period_debits,
               COALESCE(pm.period_credits, 0) as period_credits
        FROM accounts a
        LEFT JOIN initial_balances ib ON a.id = ib.account_id
        LEFT JOIN period_movements pm ON a.id = pm.account_id
        WHERE a.is_active = true
          AND (ib.account_id IS NOT NULL OR pm.account_id IS NOT NULL)
        ORDER BY a.code
    """, (period_start, period_start, period_end))

    rows = []
    for r in cur.fetchall():
        prev_debits = float(r[5])
        prev_credits = float(r[6])
        saldo_inicial = prev_debits - prev_credits
        cargos = float(r[7])
        abonos = float(r[8])
        saldo_final = saldo_inicial + cargos - abonos

        rows.append({
            'account_id': r[0], 'code': r[1], 'name': r[2],
            'type': r[3], 'parent_id': r[4],
            'saldo_inicial': round(saldo_inicial, 2),
            'cargos': round(cargos, 2),
            'abonos': round(abonos, 2),
            'saldo_final': round(saldo_final, 2),
        })

    cur.close()
    conn.close()
    return jsonify({'data': rows, 'period': {'year': year, 'month': month}})


@accounting_bp.route('/income-statement', methods=['GET'])
@require_auth('accounting.view')
def income_statement():
    """Estado de resultados for a period.

    Query params: year, month

    Groups: Ingresos (400), Costos (500), Gastos (600)
    Result: Ingresos - Costos - Gastos = Utilidad/Perdida
    """
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    year = int(request.args.get('year', date.today().year))
    month = int(request.args.get('month', date.today().month))

    period_start = f'{year}-{month:02d}-01'
    if month == 12:
        period_end = f'{year + 1}-01-01'
    else:
        period_end = f'{year}-{month + 1:02d}-01'

    cur.execute("""
        SELECT a.id, a.code, a.name, a.type,
               COALESCE(SUM(l.debit), 0) as total_debit,
               COALESCE(SUM(l.credit), 0) as total_credit
        FROM accounts a
        LEFT JOIN journal_entry_lines l ON a.id = l.account_id
        LEFT JOIN journal_entries e ON l.journal_entry_id = e.id
            AND e.status = 'posted'
            AND e.date >= %s AND e.date < %s
        WHERE a.type IN ('ingreso', 'costo', 'gasto')
          AND a.is_active = true
          AND a.parent_id IS NOT NULL
        GROUP BY a.id, a.code, a.name, a.type
        HAVING COALESCE(SUM(l.debit), 0) != 0 OR COALESCE(SUM(l.credit), 0) != 0
        ORDER BY a.code
    """, (period_start, period_end))

    ingresos = []
    costos = []
    gastos = []
    total_ingresos = 0
    total_costos = 0
    total_gastos = 0

    for r in cur.fetchall():
        debit = float(r[4])
        credit = float(r[5])
        # For income accounts: credit - debit = revenue
        # For cost/expense accounts: debit - credit = expense
        if r[3] == 'ingreso':
            amount = round(credit - debit, 2)
            total_ingresos += amount
            ingresos.append({'code': r[1], 'name': r[2], 'amount': amount})
        elif r[3] == 'costo':
            amount = round(debit - credit, 2)
            total_costos += amount
            costos.append({'code': r[1], 'name': r[2], 'amount': amount})
        elif r[3] == 'gasto':
            amount = round(debit - credit, 2)
            total_gastos += amount
            gastos.append({'code': r[1], 'name': r[2], 'amount': amount})

    utilidad_bruta = round(total_ingresos - total_costos, 2)
    utilidad_neta = round(utilidad_bruta - total_gastos, 2)

    cur.close()
    conn.close()

    return jsonify({
        'period': {'year': year, 'month': month},
        'ingresos': {'items': ingresos, 'total': round(total_ingresos, 2)},
        'costos': {'items': costos, 'total': round(total_costos, 2)},
        'utilidad_bruta': utilidad_bruta,
        'gastos': {'items': gastos, 'total': round(total_gastos, 2)},
        'utilidad_neta': utilidad_neta,
    })


@accounting_bp.route('/balance-sheet', methods=['GET'])
@require_auth('accounting.view')
def balance_sheet():
    """Balance general as of a given date.

    Query params: date (YYYY-MM-DD, default today)

    Groups: Activo (100), Pasivo (200), Capital (300)
    Validates: Activo = Pasivo + Capital
    """
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    as_of = request.args.get('date', str(date.today()))

    cur.execute("""
        SELECT a.id, a.code, a.name, a.type, a.parent_id,
               COALESCE(SUM(l.debit), 0) as total_debit,
               COALESCE(SUM(l.credit), 0) as total_credit
        FROM accounts a
        LEFT JOIN journal_entry_lines l ON a.id = l.account_id
        LEFT JOIN journal_entries e ON l.journal_entry_id = e.id
            AND e.status = 'posted'
            AND e.date <= %s
        WHERE a.type IN ('activo', 'pasivo', 'capital')
          AND a.is_active = true
          AND a.parent_id IS NOT NULL
        GROUP BY a.id, a.code, a.name, a.type, a.parent_id
        HAVING COALESCE(SUM(l.debit), 0) != 0 OR COALESCE(SUM(l.credit), 0) != 0
        ORDER BY a.code
    """, (as_of,))

    activo = []
    pasivo = []
    capital_items = []
    total_activo = 0
    total_pasivo = 0
    total_capital = 0

    for r in cur.fetchall():
        debit = float(r[5])
        credit = float(r[6])
        if r[3] == 'activo':
            balance = round(debit - credit, 2)
            total_activo += balance
            activo.append({'code': r[1], 'name': r[2], 'balance': balance})
        elif r[3] == 'pasivo':
            balance = round(credit - debit, 2)
            total_pasivo += balance
            pasivo.append({'code': r[1], 'name': r[2], 'balance': balance})
        elif r[3] == 'capital':
            balance = round(credit - debit, 2)
            total_capital += balance
            capital_items.append({'code': r[1], 'name': r[2], 'balance': balance})

    # Include current period net income in capital
    # (Ingresos - Costos - Gastos for the current year)
    cur.execute("""
        SELECT a.type,
               COALESCE(SUM(l.debit), 0) as total_debit,
               COALESCE(SUM(l.credit), 0) as total_credit
        FROM accounts a
        JOIN journal_entry_lines l ON a.id = l.account_id
        JOIN journal_entries e ON l.journal_entry_id = e.id
        WHERE e.status = 'posted' AND e.date <= %s
          AND a.type IN ('ingreso', 'costo', 'gasto')
        GROUP BY a.type
    """, (as_of,))

    net_income = 0
    for r in cur.fetchall():
        debit = float(r[1])
        credit = float(r[2])
        if r[0] == 'ingreso':
            net_income += (credit - debit)
        elif r[0] in ('costo', 'gasto'):
            net_income -= (debit - credit)

    net_income = round(net_income, 2)
    total_capital += net_income
    capital_items.append({
        'code': '320', 'name': 'Resultado del ejercicio', 'balance': net_income
    })

    cur.close()
    conn.close()

    return jsonify({
        'as_of': as_of,
        'activo': {'items': activo, 'total': round(total_activo, 2)},
        'pasivo': {'items': pasivo, 'total': round(total_pasivo, 2)},
        'capital': {'items': capital_items, 'total': round(total_capital, 2)},
        'balanced': round(total_activo, 2) == round(total_pasivo + total_capital, 2),
    })


@accounting_bp.route('/aging', methods=['GET'])
@require_auth('accounting.view')
def aging_report():
    """Antiguedad de saldos.

    Returns individual credit sales with outstanding balance when type=receivable
    (default), or purchase orders payable to suppliers when type=payable.
    """
    report_type = request.args.get('type', 'receivable')
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    rows = []
    if report_type == 'payable':
        # Accounts payable: purchase orders to suppliers that are not paid/cancelled
        cur.execute("""
            SELECT po.id, po.supplier_invoice, po.total, po.created_at, po.expected_date,
                   po.status, s.id, s.name
            FROM purchase_orders po
            JOIN suppliers s ON s.id = po.supplier_id
            WHERE po.status NOT IN ('paid', 'cancelled')
            ORDER BY po.created_at DESC
        """)
        for r in cur.fetchall():
            po_id = r[0]
            invoice = r[1] or f'OC-{po_id}'
            total = float(r[2]) if r[2] else 0
            created_at = r[3]
            expected_date = r[4]
            status = r[5]
            vendor_name = r[7]

            paid = 0  # TODO: sum supplier payments when that table is added
            balance = round(total - paid, 2)
            if balance <= 0:
                continue

            days_overdue = (datetime.now(created_at.tzinfo) - expected_date).days if expected_date and created_at else 0
            if days_overdue > 0:
                po_status = 'overdue'
                label = 'Vencida'
            elif paid > 0:
                po_status = 'partial'
                label = 'Parcial'
            else:
                po_status = 'pending'
                label = 'Pendiente'

            rows.append({
                'po_id': po_id,
                'invoice': invoice,
                'vendor_name': vendor_name,
                'issue_date': created_at.isoformat() if created_at else None,
                'due_date': expected_date.isoformat() if expected_date else None,
                'total': total,
                'paid': paid,
                'balance': balance,
                'days_overdue': days_overdue,
                'status': po_status,
                'status_label': label,
            })
    else:
        # Accounts receivable: credit sales to customers
        cur.execute("""
            SELECT s.id, s.total, s.created_at, s.status,
                   c.id, c.name, c.rfc,
                   COALESCE((SELECT SUM(amount) FROM sale_payments sp WHERE sp.sale_id = s.id), 0) as payments_total
            FROM sales s
            JOIN customers c ON c.id = s.customer_id
            WHERE s.sale_type = 'credit'
              AND s.status = 'completed'
            ORDER BY s.created_at DESC
        """)
        for r in cur.fetchall():
            sale_id = r[0]
            total = float(r[1]) if r[1] else 0
            created_at = r[2]
            status = r[3]
            customer_name = r[5]
            payments_total = float(r[7]) if r[7] else 0
            paid = payments_total
            balance = round(total - paid, 2)

            if balance <= 0:
                continue

            due_date = created_at + timedelta(days=30) if created_at else None
            days_overdue = (datetime.now(created_at.tzinfo) - due_date).days if due_date else 0

            if days_overdue > 0:
                sale_status = 'overdue'
                label = 'Vencida'
            elif paid > 0:
                sale_status = 'partial'
                label = 'Parcial'
            else:
                sale_status = 'pending'
                label = 'Vigente'

            rows.append({
                'sale_id': sale_id,
                'invoice': f'VTA-{sale_id}',
                'customer_name': customer_name,
                'issue_date': created_at.isoformat() if created_at else None,
                'due_date': due_date.isoformat() if due_date else None,
                'total': total,
                'paid': paid,
                'balance': balance,
                'days_overdue': days_overdue,
                'status': sale_status,
                'status_label': label,
            })

    totals = {
        'count': len(rows),
        'total': round(sum(r['total'] for r in rows), 2),
        'paid': round(sum(r['paid'] for r in rows), 2),
        'balance': round(sum(r['balance'] for r in rows), 2),
    }

    cur.close()
    conn.close()
    return jsonify({'data': rows, 'totals': totals})


@accounting_bp.route('/aging-summary', methods=['GET'])
@require_auth('accounting.view')
def aging_summary():
    """Customer-level accounts receivable summary (used by reports)."""
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    cur.execute("""
        SELECT c.id, c.name, c.rfc, c.credit_limit, c.credit_balance,
               s.id as sale_id,
               s.total - COALESCE((SELECT SUM(amount) FROM sale_payments sp WHERE sp.sale_id = s.id), 0) as balance,
               s.created_at,
               EXTRACT(DAY FROM NOW() - s.created_at)::int as days_outstanding
        FROM customers c
        JOIN sales s ON s.customer_id = c.id
        WHERE s.sale_type = 'credit'
          AND s.status = 'completed'
          AND c.credit_balance > 0
        ORDER BY c.name, s.created_at
    """)

    customers = {}
    for r in cur.fetchall():
        cust_id = r[0]
        balance = round(float(r[6]) if r[6] else 0, 2)
        if balance <= 0:
            continue
        days = r[8] or 0

        if cust_id not in customers:
            customers[cust_id] = {
                'id': r[0], 'name': r[1], 'rfc': r[2],
                'credit_limit': float(r[3]) if r[3] else 0,
                'credit_balance': float(r[4]) if r[4] else 0,
                'corriente': 0, 'd1_30': 0, 'd31_60': 0, 'd61_90': 0, 'd90_plus': 0,
                'total': 0,
            }

        if days <= 0:
            customers[cust_id]['corriente'] += balance
        elif days <= 30:
            customers[cust_id]['d1_30'] += balance
        elif days <= 60:
            customers[cust_id]['d31_60'] += balance
        elif days <= 90:
            customers[cust_id]['d61_90'] += balance
        else:
            customers[cust_id]['d90_plus'] += balance

        customers[cust_id]['total'] += balance

    result = list(customers.values())
    for c in result:
        c['credit_balance'] = round(c['total'], 2)
        for key in ('corriente', 'd1_30', 'd31_60', 'd61_90', 'd90_plus', 'total'):
            c[key] = round(c[key], 2)

    totals = {
        'corriente': round(sum(c['corriente'] for c in result), 2),
        'd1_30': round(sum(c['d1_30'] for c in result), 2),
        'd31_60': round(sum(c['d31_60'] for c in result), 2),
        'd61_90': round(sum(c['d61_90'] for c in result), 2),
        'd90_plus': round(sum(c['d90_plus'] for c in result), 2),
        'total': round(sum(c['total'] for c in result), 2),
        'count': len(result),
    }

    cur.close()
    conn.close()
    return jsonify({'data': result, 'totals': totals})


@accounting_bp.route('/aging/export', methods=['GET'])
@require_auth('accounting.view')
def export_aging():
    """Export receivables or payables to CSV/PDF."""
    report_type = request.args.get('type', 'receivable')
    fmt = request.args.get('format', 'csv').lower()
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    if report_type == 'payable':
        # Payables always CSV for now
        output = io.StringIO()
        writer = csv.writer(output)
        writer.writerow(['OC', 'Proveedor', 'Fecha emision', 'Fecha vencimiento',
                         'Total', 'Pagado', 'Saldo', 'Dias vencido', 'Estado'])
        cur.execute("""
            SELECT po.id, po.supplier_invoice, po.total, po.created_at, po.expected_date,
                   po.status, s.name
            FROM purchase_orders po
            JOIN suppliers s ON s.id = po.supplier_id
            WHERE po.status NOT IN ('paid', 'cancelled')
            ORDER BY po.created_at DESC
        """)
        for r in cur.fetchall():
            po_id = r[0]
            invoice = r[1] or f'OC-{po_id}'
            total = float(r[2]) if r[2] else 0
            created_at = r[3]
            expected = r[4]
            status = r[5]
            vendor = r[6]
            paid = 0
            balance = round(total - paid, 2)
            days_overdue = (datetime.now(created_at.tzinfo) - expected).days if expected and created_at else 0
            status_label = 'Vencida' if days_overdue > 0 else 'Pendiente'
            writer.writerow([
                invoice, vendor,
                created_at.strftime('%Y-%m-%d') if created_at else '',
                expected.strftime('%Y-%m-%d') if expected else '',
                total, paid, balance, days_overdue, status_label
            ])
        cur.close(); conn.close()
        csv_data = output.getvalue()
        output.close()
        return Response(
            '\ufeff' + csv_data,
            mimetype='text/csv; charset=utf-8',
            headers={'Content-Disposition': f'attachment; filename=cuentas_por_pagar_{date.today().isoformat()}.csv'}
        )

    # Receivables: CSV or PDF
    cur.execute("""
        SELECT s.id, s.subtotal, s.tax_total, s.total, s.created_at,
               c.name,
               COALESCE((SELECT SUM(amount) FROM sale_payments sp WHERE sp.sale_id = s.id), 0) as payments_total
        FROM sales s
        JOIN customers c ON c.id = s.customer_id
        WHERE s.sale_type = 'credit'
          AND s.status = 'completed'
        ORDER BY s.created_at DESC
    """)

    rows = []
    for r in cur.fetchall():
        sale_id = r[0]
        subtotal = float(r[1]) if r[1] else 0
        tax = float(r[2]) if r[2] else 0
        total = float(r[3]) if r[3] else 0
        created_at = r[4]
        customer_name = r[5]
        payments_total = float(r[6]) if r[6] else 0
        balance = round(total - payments_total, 2)
        if balance <= 0:
            continue
        rows.append({
            'date': created_at.strftime('%d/%m/%Y') if created_at else '',
            'customer': customer_name,
            'subtotal': subtotal,
            'tax': tax,
            'total': total,
            'balance': balance,
        })

    cur.close(); conn.close()

    if fmt == 'pdf':
        from fpdf import FPDF

        class ReceivablesPDF(FPDF):
            def header(self):
                self.set_font('Arial', 'B', 14)
                self.cell(0, 10, 'Reporte de Ventas por Cobrar', 0, 1, 'C')
                self.set_font('Arial', '', 10)
                self.cell(0, 6, f'Generado: {date.today().strftime("%d/%m/%Y")}', 0, 1, 'C')
                self.ln(4)

            def footer(self):
                self.set_y(-15)
                self.set_font('Arial', 'I', 8)
                self.cell(0, 10, f'Pagina {self.page_no()}', 0, 0, 'C')

        pdf = ReceivablesPDF('L', 'mm', 'A4')
        pdf.add_page()
        pdf.set_font('Arial', 'B', 10)
        pdf.set_fill_color(230, 230, 230)

        col_widths = [30, 95, 35, 30, 35, 35]
        headers = ['FECHA', 'CLIENTE', 'SUBTOTAL', 'IVA', 'TOTAL', 'SALDO']
        for i, h in enumerate(headers):
            pdf.cell(col_widths[i], 10, h, 1, 0, 'C', True)
        pdf.ln()

        pdf.set_font('Arial', '', 9)
        totals = {'subtotal': 0, 'tax': 0, 'total': 0, 'balance': 0}
        for row in rows:
            pdf.cell(col_widths[0], 8, row['date'], 1, 0, 'C')
            pdf.cell(col_widths[1], 8, row['customer'][:50], 1, 0, 'L')
            pdf.cell(col_widths[2], 8, f"{row['subtotal']:.2f}", 1, 0, 'R')
            pdf.cell(col_widths[3], 8, f"{row['tax']:.2f}", 1, 0, 'R')
            pdf.cell(col_widths[4], 8, f"{row['total']:.2f}", 1, 0, 'R')
            pdf.cell(col_widths[5], 8, f"{row['balance']:.2f}", 1, 1, 'R')
            totals['subtotal'] += row['subtotal']
            totals['tax'] += row['tax']
            totals['total'] += row['total']
            totals['balance'] += row['balance']

        pdf.set_font('Arial', 'B', 9)
        pdf.cell(col_widths[0] + col_widths[1], 8, 'TOTAL', 1, 0, 'R', True)
        pdf.cell(col_widths[2], 8, f"{totals['subtotal']:.2f}", 1, 0, 'R', True)
        pdf.cell(col_widths[3], 8, f"{totals['tax']:.2f}", 1, 0, 'R', True)
        pdf.cell(col_widths[4], 8, f"{totals['total']:.2f}", 1, 0, 'R', True)
        pdf.cell(col_widths[5], 8, f"{totals['balance']:.2f}", 1, 1, 'R', True)

        pdf_bytes = bytes(pdf.output(dest='S'))
        return Response(
            pdf_bytes,
            mimetype='application/pdf',
            headers={'Content-Disposition': f'attachment; filename=ventas_por_cobrar_{date.today().isoformat()}.pdf'}
        )

    # CSV fallback
    output = io.StringIO()
    writer = csv.writer(output)
    writer.writerow(['Fecha', 'Cliente', 'Subtotal', 'IVA', 'Total', 'Saldo'])
    for row in rows:
        writer.writerow([row['date'], row['customer'], row['subtotal'], row['tax'], row['total'], row['balance']])
    csv_data = output.getvalue()
    output.close()
    return Response(
        '\ufeff' + csv_data,
        mimetype='text/csv; charset=utf-8',
        headers={'Content-Disposition': f'attachment; filename=ventas_por_cobrar_{date.today().isoformat()}.csv'}
    )


# ─── Fiscal Periods ────────────────────────────────

@accounting_bp.route('/periods', methods=['GET'])
@require_auth('accounting.view')
def list_periods():
    """List fiscal periods with status."""
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    cur.execute("""
        SELECT fp.id, fp.year, fp.month, fp.status,
               fp.closed_by, e.name as closed_by_name, fp.closed_at
        FROM fiscal_periods fp
        LEFT JOIN employees e ON fp.closed_by = e.id
        ORDER BY fp.year DESC, fp.month DESC
    """)

    periods = []
    for r in cur.fetchall():
        periods.append({
            'id': r[0], 'year': r[1], 'month': r[2],
            'status': r[3], 'closed_by': r[4],
            'closed_by_name': r[5],
            'closed_at': str(r[6]) if r[6] else None,
        })

    cur.close()
    conn.close()
    return jsonify({'data': periods})


@accounting_bp.route('/periods/close', methods=['POST'])
@require_auth('accounting.create')
def close_period():
    """Close a fiscal period. Owner only.

    Body: {year: int, month: int}

    Closing a period:
    1. Validates the period is currently open
    2. Prevents future journal entries in that period
    3. Only the owner can close periods
    """
    if g.employee_role != 'owner':
        return jsonify({'error': 'Only the owner can close fiscal periods'}), 403

    data = request.get_json() or {}
    year = data.get('year')
    month = data.get('month')

    if not year or not month:
        return jsonify({'error': 'year and month are required'}), 400

    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    try:
        # Check if period already exists
        cur.execute("""
            SELECT id, status FROM fiscal_periods WHERE year = %s AND month = %s
        """, (year, month))
        row = cur.fetchone()

        if row and row[1] == 'closed':
            return jsonify({'error': f'Period {year}-{month:02d} is already closed'}), 409

        if row:
            # Update existing
            cur.execute("""
                UPDATE fiscal_periods
                SET status = 'closed', closed_by = %s, closed_at = NOW()
                WHERE id = %s
            """, (g.employee_id, row[0]))
            period_id = row[0]
        else:
            # Create and close
            cur.execute("""
                INSERT INTO fiscal_periods (year, month, status, closed_by, closed_at)
                VALUES (%s, %s, 'closed', %s, NOW())
                RETURNING id
            """, (year, month, g.employee_id))
            period_id = cur.fetchone()[0]

        log_action(conn, 'PERIOD_CLOSED', 'fiscal_period', period_id,
                   new_value={'year': year, 'month': month})

        conn.commit()
        cur.close()
        conn.close()

        return jsonify({
            'id': period_id,
            'year': year,
            'month': month,
            'status': 'closed',
            'message': f'Period {year}-{month:02d} closed successfully'
        })

    except Exception as e:
        conn.rollback()
        cur.close()
        conn.close()
        return jsonify({'error': str(e)}), 500


@accounting_bp.route('/stats', methods=['GET'])
@require_auth('accounting.view')
def api_accounting_stats():
    """Return counts for tab badges: receivables (asset accounts with balance) and payables (liability accounts with balance)."""
    conn = get_tenant_conn(g.tenant_id)
    cur = conn.cursor()

    # Count asset accounts with positive balance (cuentas por cobrar)
    cur.execute("""
        SELECT COUNT(*) FROM (
            SELECT a.id
            FROM accounts a
            LEFT JOIN journal_entry_lines l ON l.account_id = a.id
            WHERE a.type = 'activo' AND a.is_active = true
            GROUP BY a.id
            HAVING COALESCE(SUM(l.debit), 0) - COALESCE(SUM(l.credit), 0) > 0
        ) x
    """)
    cxc = cur.fetchone()[0] or 0

    # Count liability accounts with positive balance (cuentas por pagar)
    cur.execute("""
        SELECT COUNT(*) FROM (
            SELECT a.id
            FROM accounts a
            LEFT JOIN journal_entry_lines l ON l.account_id = a.id
            WHERE a.type = 'pasivo' AND a.is_active = true
            GROUP BY a.id
            HAVING COALESCE(SUM(l.credit), 0) - COALESCE(SUM(l.debit), 0) > 0
        ) x
    """)
    cxp = cur.fetchone()[0] or 0

    cur.close()
    conn.close()

    return jsonify({
        'cuentas_cobrar': cxc,
        'cuentas_pagar': cxp,
    })
