"""
Yield payout generation helpers (ledger).

Used by:
- Admin endpoints (manual ops)
- Scheduler (automatic monthly preparation)
"""

from __future__ import annotations

from datetime import datetime, timedelta
from decimal import Decimal

from sqlalchemy import and_, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.yield_domain import YieldDomain, YieldPayout, YieldTransaction


async def generate_payouts_for_period(
    db: AsyncSession,
    *,
    period_start: datetime,
    period_end: datetime,
) -> tuple[int, int]:
    """
    Create payouts for confirmed, unpaid transactions and assign payout_id.

    Returns: (created_count, skipped_existing_count)
    """
    if period_end <= period_start:
        raise ValueError("period_end must be after period_start")

    aggregates = (
        await db.execute(
            select(
                YieldDomain.user_id.label("user_id"),
                YieldTransaction.currency.label("currency"),
                func.count(YieldTransaction.id).label("tx_count"),
                func.coalesce(func.sum(YieldTransaction.net_amount), 0).label("amount"),
            )
            .join(YieldDomain, YieldDomain.id == YieldTransaction.yield_domain_id)
            .where(
                and_(
                    YieldTransaction.status == "confirmed",
                    YieldTransaction.payout_id.is_(None),
                    YieldTransaction.created_at >= period_start,
                    YieldTransaction.created_at < period_end,
                )
            )
            .group_by(YieldDomain.user_id, YieldTransaction.currency)
        )
    ).all()

    created = 0
    skipped = 0

    for row in aggregates:
        user_id = int(row.user_id)
        currency = (row.currency or "CHF").upper()
        tx_count = int(row.tx_count or 0)
        amount = Decimal(str(row.amount or 0))

        if tx_count <= 0 or amount <= 0:
            continue

        existing = (
            await db.execute(
                select(YieldPayout).where(
                    and_(
                        YieldPayout.user_id == user_id,
                        YieldPayout.currency == currency,
                        YieldPayout.period_start == period_start,
                        YieldPayout.period_end == period_end,
                    )
                )
            )
        ).scalar_one_or_none()
        if existing:
            skipped += 1
            continue

        payout = YieldPayout(
            user_id=user_id,
            amount=amount,
            currency=currency,
            period_start=period_start,
            period_end=period_end,
            transaction_count=tx_count,
            status="pending",
            payment_method=None,
            payment_reference=None,
        )
        db.add(payout)
        await db.flush()

        tx_ids = (
            await db.execute(
                select(YieldTransaction.id)
                .join(YieldDomain, YieldDomain.id == YieldTransaction.yield_domain_id)
                .where(
                    and_(
                        YieldDomain.user_id == user_id,
                        YieldTransaction.currency == currency,
                        YieldTransaction.status == "confirmed",
                        YieldTransaction.payout_id.is_(None),
                        YieldTransaction.created_at >= period_start,
                        YieldTransaction.created_at < period_end,
                    )
                )
            )
        ).scalars().all()

        for tx_id in tx_ids:
            tx = (
                await db.execute(select(YieldTransaction).where(YieldTransaction.id == tx_id))
            ).scalar_one()
            tx.payout_id = payout.id

        created += 1

    await db.commit()
    return created, skipped


async def generate_payouts_for_previous_month(db: AsyncSession) -> tuple[int, int]:
    now = datetime.utcnow()
    month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
    prev_month_end = month_start
    prev_month_start = (month_start - timedelta(days=1)).replace(day=1)
    return await generate_payouts_for_period(db, period_start=prev_month_start, period_end=prev_month_end)

