
import asyncio
from dataclasses import dataclass
from typing import Sequence

@dataclass(frozen=True)
class Reservation:
    node_id: str
    gpu_count: int
    price_per_hour: float
    fabric: str

    @property
    def hourly_total(self) -> float:
        return self.gpu_count * self.price_per_hour


async def fetch_offers(client, region: str, *, limit: int = 100) -> Sequence[Reservation]:
    response = await client.get(f"/v1/offers?region={region}&limit={limit}")
    response.raise_for_status()
    return [
        Reservation(
            node_id=row["id"],
            gpu_count=int(row["gpu_count"]),
            price_per_hour=float(row["price_per_gpu_hour"]),
            fabric=row.get("fabric", "unknown"),
        )
        for row in response.json()["data"]
    ]


def cheapest_by_fabric(offers, fabric):
    matching = [o for o in offers if o.fabric == fabric]
    if not matching:
        raise ValueError(f"no offers with fabric={fabric!r}")
    return min(matching, key=lambda o: o.price_per_hour)

SELECT
    provider_id,
    gpu_model,
    DATE_TRUNC('day', executed_at) AS trade_date,
    SUM(notional_usd) / NULLIF(SUM(gpu_hours), 0) AS realized_price,
    COUNT(*) AS trade_count
FROM transactions
WHERE executed_at >= NOW() - INTERVAL '30 days'
  AND settlement_status = 'cleared'
GROUP BY 1, 2, 3
HAVING COUNT(*) >= 5
ORDER BY trade_date DESC, realized_price ASC;

export async function normalizePrices(
  rows: PriceRow[],
  factors: Record<string, number>,
): Promise<NormalizedRow[]> {
  return rows.map((row) => {
    const factor = factors[row.providerId] ?? 1.0;
    return { ...row, normalizedPrice: row.rawPrice / factor, factorApplied: factor };
  });
}
