"""Task: daily workspace usage snapshot + credit exhaustion alerts.

Runs once per day via arq cron (midnight UTC).

1. Pulls Cloud service snapshots and hosted-app edge telemetry for yesterday
2. Aggregates to workspace level, writes monitor-only usage snapshots
3. Checks credit exhaustion thresholds (50%, 90%, 100%)
4. Sends notifications and applies rate-limiting or auto-deduction
"""
from __future__ import annotations

import structlog
from datetime import date, datetime, timedelta, timezone

logger = structlog.get_logger()


async def daily_usage_snapshot(ctx: dict) -> dict:
    from src.core.database import async_session_factory
    from src.domain.projects.repository import ProjectRepository
    from src.domain.usage.hosted_app_analytics import fetch_hosted_app_usage_by_project
    from src.domain.usage.repository import (
        ProjectHostedAppUsageSnapshotRepository,
        WorkspaceUsageSnapshotRepository,
    )
    from src.domain.workspaces.repository import WorkspaceRepository

    db_factory = ctx.get("db_factory", async_session_factory)
    yesterday = date.today() - timedelta(days=1)
    snapshots_written = alerts_sent = 0

    hosted_app_usage = await fetch_hosted_app_usage_by_project(yesterday)

    async with db_factory() as db:
        snapshot_repo = WorkspaceUsageSnapshotRepository(db)
        hosted_app_snapshot_repo = ProjectHostedAppUsageSnapshotRepository(db)
        workspace_repo = WorkspaceRepository(db)
        projects = await ProjectRepository(db).list_by_ids(list(hosted_app_usage))
        hosted_app_usage_by_workspace: dict = {}
        for project in projects:
            usage = hosted_app_usage[project.id]
            await hosted_app_snapshot_repo.upsert(
                workspace_id=project.workspace_id,
                project_id=project.id,
                snapshot_date=yesterday,
                requests=usage["hosted_app_requests"],
                egress_bytes=usage["hosted_app_egress_bytes"],
            )
            totals = hosted_app_usage_by_workspace.setdefault(
                project.workspace_id,
                {"hosted_app_requests": 0, "hosted_app_egress_bytes": 0},
            )
            totals["hosted_app_requests"] += usage["hosted_app_requests"]
            totals["hosted_app_egress_bytes"] += usage["hosted_app_egress_bytes"]

        # Pull per-workspace project snapshots from Cloud
        workspaces = await workspace_repo.list_active()
        for ws in workspaces:
            try:
                project_data = await _fetch_cloud_project_snapshots(str(ws.id), yesterday)
                traffic = hosted_app_usage_by_workspace.get(ws.id, {})
                if not project_data and not traffic:
                    continue

                totals = _aggregate_metrics(project_data)
                totals.update(traffic)
                await snapshot_repo.upsert(
                    workspace_id=ws.id,
                    snapshot_date=yesterday,
                    **totals,
                )
                snapshots_written += 1

                await _settle_cloud_credits(db, ws.id, yesterday, project_data, totals)
                notified = await _check_wallet_thresholds(db, ws.id)
                alerts_sent += notified
            except Exception as exc:
                logger.error(
                    "usage_snapshot_error",
                    workspace_id=str(ws.id),
                    error=str(exc),
                )

        await db.commit()

    logger.info(
        "daily_usage_snapshot_complete",
        date=str(yesterday),
        snapshots_written=snapshots_written,
        alerts_sent=alerts_sent,
    )
    return {
        "status": "ok",
        "date": str(yesterday),
        "snapshots_written": snapshots_written,
        "alerts_sent": alerts_sent,
    }


async def _fetch_cloud_project_snapshots(
    workspace_id: str, snapshot_date: date
) -> list[dict]:
    """Ask Cloud for per-project usage snapshots for a given workspace + date."""
    from src.core.database import async_session_factory
    from src.integrations.cloud_control_client import get_cloud_client_for_workspace
    from uuid import UUID

    async with async_session_factory() as db:
        client = await get_cloud_client_for_workspace(workspace_id, db)

    try:
        result = await client.get_project_usage_snapshots(
            workspace_id=workspace_id,
            snapshot_date=snapshot_date.isoformat(),
        )
        return result.get("snapshots", [])
    except Exception as exc:
        logger.warning(
            "cloud_usage_fetch_error",
            workspace_id=workspace_id,
            date=str(snapshot_date),
            error=str(exc),
        )
        return []


def _aggregate_metrics(project_data: list[dict]) -> dict:
    totals: dict[str, float] = {
        "cpu_minutes": 0, "db_reads": 0, "db_writes": 0,
        "storage_gb": 0, "cdn_gb": 0, "auth_users_active": 0,
        "hosted_app_requests": 0, "hosted_app_egress_bytes": 0,
        "ai_embed_tokens": 0, "ai_completion_tokens": 0, "ai_searches": 0,
        "job_executions": 0, "realtime_minutes": 0, "ext_queries": 0,
        "credits_consumed": 0,
    }
    for p in project_data:
        for key in totals:
            totals[key] += float(p.get(key, 0))
    return totals


_CLOUD_SERVICE_METRIC_KEYS = (
    "cpu_minutes", "db_reads", "db_writes", "storage_gb", "job_executions",
    "ai_embed_tokens", "ai_completion_tokens", "ai_searches", "ext_queries",
)


async def _settle_cloud_credits(
    db, workspace_id, snapshot_date: date, project_data: list[dict], totals: dict
) -> None:
    """Settle one workspace's daily wallet debit.

    Per-project markup/fixed-fee (ProjectBillingExtras) applies only to metered
    Cloud/backend service usage, computed per project from `project_data` before
    summing — CF already measures hosted-app requests/egress directly, so there's
    nothing "uncounted" there to add a surcharge for; that portion is priced once
    at the workspace level from the aggregated totals, unmarked-up.
    """
    import calendar
    from uuid import UUID

    from src.domain.cloud_credits.conversion import apply_project_markup_and_fee, millicredits_from_metrics
    from src.domain.cloud_credits.repository import CloudUsageConversionRateRepository, ProjectBillingExtrasRepository
    from src.domain.cloud_credits.service import CloudCreditService
    from src.domain.hosting.public_serving_gate import HostingPublicServingGate

    metrics = {
        key: float(totals.get(key, 0) or 0)
        for key in ("hosted_app_requests", "hosted_app_egress_bytes", *_CLOUD_SERVICE_METRIC_KEYS)
    }

    rates = await CloudUsageConversionRateRepository(db).list_active()
    extras_repo = ProjectBillingExtrasRepository(db)
    days_in_month = calendar.monthrange(snapshot_date.year, snapshot_date.month)[1]

    total_millicredits = 0
    for p in project_data:
        project_id = p.get("project_id")
        if not project_id:
            continue
        project_metrics = {key: float(p.get(key, 0) or 0) for key in _CLOUD_SERVICE_METRIC_KEYS}
        base_millicredits = millicredits_from_metrics(project_metrics, rates)
        extras = await extras_repo.get_or_default(UUID(project_id))
        total_millicredits += apply_project_markup_and_fee(
            base_millicredits,
            markup_percentage=extras.markup_percentage,
            fixed_monthly_fee_credits=extras.fixed_monthly_fee_credits,
            days_in_month=days_in_month,
        )

    hosted_app_metrics = {
        "hosted_app_requests": metrics["hosted_app_requests"],
        "hosted_app_egress_bytes": metrics["hosted_app_egress_bytes"],
    }
    total_millicredits += millicredits_from_metrics(hosted_app_metrics, rates)

    await CloudCreditService(db=db, serving_gate=HostingPublicServingGate()).settle_daily_usage(
        workspace_id, snapshot_date.isoformat(), metrics, millicredits_override=total_millicredits,
    )


async def _check_wallet_thresholds(db, workspace_id) -> int:
    """Email billing.manage members at 80% and 100% per wallet. Cloud only when capped."""
    from src.domain.admin.repository import SubscriptionPlanRepository
    from src.domain.billing.balance_service import WorkspaceUsageBalanceService
    from src.domain.billing.repository import (
        SubscriptionRepository,
        WorkspaceCreditLedgerRepository,
        WorkspaceUsageAllowanceRepository,
    )
    from src.domain.cloud_credits.conversion import credits_from_millicredits
    from src.domain.cloud_credits.repository import (
        CloudCreditLedgerRepository,
        WorkspaceCloudCreditPolicyRepository,
        WorkspaceWalletAlertRepository,
    )
    from src.workers.enqueue import enqueue_billing_email

    alerts = 0
    alert_repo = WorkspaceWalletAlertRepository(db)
    sub = await SubscriptionRepository(db).get_by_workspace(workspace_id)
    period_start = sub.current_period_start.date() if sub and sub.current_period_start else date.today().replace(day=1)
    period_start_dt = sub.current_period_start if sub and sub.current_period_start else None

    balance = await WorkspaceUsageBalanceService(
        subscription_repo=SubscriptionRepository(db),
        plan_repo=SubscriptionPlanRepository(db),
        allowance_repo=WorkspaceUsageAllowanceRepository(db),
        ledger_repo=WorkspaceCreditLedgerRepository(db),
    ).get_balance(workspace_id)
    allowance = await WorkspaceUsageAllowanceRepository(db).get_current_for_workspace(workspace_id)
    included = allowance.included_units if allowance else 0
    consumed = allowance.consumed_units if allowance else 0
    pool = included + balance.purchased_topup_remaining + consumed
    if pool > 0:
        pct = consumed / pool
        sent = await _notify_wallet_threshold(
            db,
            alert_repo,
            enqueue_billing_email,
            workspace_id,
            "codegen",
            period_start,
            pct,
            consumed,
        )
        alerts += sent

    policy = await WorkspaceCloudCreditPolicyRepository(db).get(workspace_id)
    if policy and policy.enforcement_enabled and policy.cap_credits and period_start_dt:
        consumed_cloud = abs(
            await CloudCreditLedgerRepository(db).sum_consumption_since(workspace_id, period_start_dt)
        )
        pct = credits_from_millicredits(consumed_cloud) / policy.cap_credits
        sent = await _notify_wallet_threshold(
            db,
            alert_repo,
            enqueue_billing_email,
            workspace_id,
            "cloud",
            period_start,
            pct,
            credits_from_millicredits(consumed_cloud),
        )
        alerts += sent

    return alerts


async def _notify_wallet_threshold(
    db,
    alert_repo,
    enqueue_billing_email,
    workspace_id,
    wallet_type: str,
    period_start: date,
    pct: float,
    credits_used: float,
) -> int:
    row = await alert_repo.get_or_create(workspace_id, wallet_type, period_start)
    now = datetime.now(timezone.utc)
    sent = 0
    if pct >= 0.8 and row.notified_80pct_at is None:
        await enqueue_billing_email(
            "send_wallet_threshold_email",
            workspace_id=str(workspace_id),
            wallet_type=wallet_type,
            pct=80,
            credits_used=credits_used,
            period_start=period_start.isoformat(),
        )
        row.notified_80pct_at = now
        sent += 1
    if pct >= 1.0 and row.notified_100pct_at is None:
        await enqueue_billing_email(
            "send_wallet_threshold_email",
            workspace_id=str(workspace_id),
            wallet_type=wallet_type,
            pct=100,
            credits_used=credits_used,
            period_start=period_start.isoformat(),
        )
        row.notified_100pct_at = now
        sent += 1
        logger.warning(
            "workspace_wallet_exhausted",
            workspace_id=str(workspace_id),
            wallet_type=wallet_type,
            credits_used=credits_used,
        )
    if sent:
        await db.flush()
    return sent
