"""VWAP — Volume-Weighted Average Price execution algorithm.

Slices a parent order according to historical intraday volume profile.
Falls back to TWAP-style equal weighting when no volume data is available.
"""

from __future__ import annotations

import math
import time
from typing import Any, Dict, List

from ..models import OrderSubmitRequest
from .base import AlgoSlice, ExecAlgorithm

# Typical U-shaped intraday volume profile (30-min buckets, 9:30-16:00 ET)
_DEFAULT_VOLUME_PROFILE = [
    0.12, 0.08, 0.06, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.06, 0.08, 0.10, 0.20,
]


class VWAPAlgorithm(ExecAlgorithm):
    """Volume-Weighted Average Price.

    Params:
        duration_minutes (float): Total execution window (default 60).
        num_slices (int): Number of child orders (default 13).
        volume_profile (list[float]): Relative volume weights per slice.
            Defaults to a typical U-shaped intraday profile.
    """

    name = "vwap"

    def generate_slices(
        self, req: OrderSubmitRequest, params: Dict[str, Any]
    ) -> List[AlgoSlice]:
        duration_min = float(params.get("duration_minutes", 60))
        profile = params.get("volume_profile", _DEFAULT_VOLUME_PROFILE)
        num_slices = int(params.get("num_slices", len(profile)))

        total_qty = req.qty or 0
        if total_qty <= 0:
            raise ValueError("VWAP requires qty > 0")

        if len(profile) < num_slices:
            profile = profile + [1.0 / num_slices] * (num_slices - len(profile))
        profile = profile[:num_slices]

        total_weight = sum(profile)
        if total_weight <= 0:
            profile = [1.0 / num_slices] * num_slices
            total_weight = 1.0
        normalized = [w / total_weight for w in profile]

        interval_sec = (duration_min * 60) / num_slices
        now = time.time()

        slices: List[AlgoSlice] = []
        allocated = 0.0
        for i, weight in enumerate(normalized):
            if i == len(normalized) - 1:
                qty = total_qty - allocated
            else:
                qty = math.floor(total_qty * weight)
            allocated += qty
            if qty <= 0:
                continue
            slices.append(
                AlgoSlice(
                    seq=i,
                    symbol=req.symbol,
                    side=req.side.value,
                    qty=qty,
                    order_type="market",
                    scheduled_time=now + i * interval_sec,
                )
            )
        return slices
