from __future__ import annotations

from collections import deque
from dataclasses import dataclass
from decimal import Decimal

from .storage import Storage


@dataclass(frozen=True)
class SellCostGuardDecision:
    approved: bool
    reason: str
    matched_cost: Decimal = Decimal("0")
    estimated_proceeds: Decimal = Decimal("0")
    oldest_opened_at_ms: int | None = None


@dataclass
class _OpenLot:
    quantity: Decimal
    cost: Decimal
    opened_at_ms: int


def evaluate_sell_cost_guard(
    storage: Storage,
    symbol: str,
    *,
    quantity: Decimal,
    bid_price: Decimal,
    now_ms: int,
    require_profit: bool,
    min_hold_seconds: int,
) -> SellCostGuardDecision:
    if not require_profit:
        return SellCostGuardDecision(True, "profit guard disabled")

    lots = _open_lots(storage, symbol)
    if not lots:
        return SellCostGuardDecision(True, "no local cost basis")

    matched_cost = Decimal("0")
    matched_quantity = Decimal("0")
    oldest_opened_at_ms: int | None = None
    remaining = quantity
    for lot in lots:
        if remaining <= 0:
            break
        take = min(remaining, lot.quantity)
        matched_quantity += take
        matched_cost += lot.cost * (take / lot.quantity)
        oldest_opened_at_ms = (
            lot.opened_at_ms if oldest_opened_at_ms is None else min(oldest_opened_at_ms, lot.opened_at_ms)
        )
        remaining -= take

    if matched_quantity <= 0:
        return SellCostGuardDecision(True, "no local cost basis")

    estimated_proceeds = matched_quantity * bid_price
    if estimated_proceeds >= matched_cost:
        return SellCostGuardDecision(
            True,
            "estimated proceeds cover cost",
            matched_cost=matched_cost,
            estimated_proceeds=estimated_proceeds,
            oldest_opened_at_ms=oldest_opened_at_ms,
        )

    hold_ms = min_hold_seconds * 1000
    if oldest_opened_at_ms is not None and now_ms - oldest_opened_at_ms >= hold_ms:
        return SellCostGuardDecision(
            True,
            "minimum hold elapsed",
            matched_cost=matched_cost,
            estimated_proceeds=estimated_proceeds,
            oldest_opened_at_ms=oldest_opened_at_ms,
        )

    return SellCostGuardDecision(
        False,
        "sell below cost before minimum hold",
        matched_cost=matched_cost,
        estimated_proceeds=estimated_proceeds,
        oldest_opened_at_ms=oldest_opened_at_ms,
    )


def _open_lots(storage: Storage, symbol: str) -> deque[_OpenLot]:
    lots: deque[_OpenLot] = deque()
    rows = _spot_trade_fills_ascending(storage, symbol)
    for row in rows:
        side = str(row["side"]).upper()
        price = Decimal(str(row["price"]))
        quantity = Decimal(str(row["quantity"]))
        quote_quantity = Decimal(str(row["quote_quantity"]))
        commission = Decimal(str(row["commission"]))
        commission_asset = str(row["commission_asset"]).upper()
        base_asset = _base_asset(str(row["symbol"]))
        quote_asset = "USDT"

        if side == "BUY":
            net_quantity = quantity - commission if commission_asset == base_asset else quantity
            cost = quote_quantity + (commission if commission_asset == quote_asset else Decimal("0"))
            if net_quantity > 0 and cost > 0:
                lots.append(_OpenLot(net_quantity, cost, int(row["created_at_ms"])))
            continue

        remaining = quantity
        while remaining > 0 and lots:
            lot = lots[0]
            take = min(remaining, lot.quantity)
            cost_take = lot.cost * (take / lot.quantity)
            lot.quantity -= take
            lot.cost -= cost_take
            remaining -= take
            if lot.quantity <= 0:
                lots.popleft()

    return lots


def _spot_trade_fills_ascending(storage: Storage, symbol: str) -> list[dict]:
    with storage._connect() as connection:
        rows = connection.execute(
            """
            SELECT * FROM spot_trade_fills
            WHERE symbol = ?
            ORDER BY created_at_ms ASC, trade_id ASC
            """,
            (symbol.upper(),),
        ).fetchall()
    return [dict(row) for row in rows]


def _base_asset(symbol: str) -> str:
    return symbol.upper().removesuffix("USDT")
