"""Deterministic cost-basis engines.

``WeightedAverageLot`` remains available for legacy position views. New portfolio
performance calculations use ``fifo_v1`` and keep lot lineage to the originating
canonical activity.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from decimal import Decimal, ROUND_HALF_UP

MONEY = Decimal("0.01")
QTY = Decimal("0.00000001")


def qmoney(value: Decimal) -> Decimal:
    return value.quantize(MONEY, rounding=ROUND_HALF_UP)


def qqty(value: Decimal) -> Decimal:
    return value.quantize(QTY, rounding=ROUND_HALF_UP).normalize()


@dataclass
class WeightedAverageLot:
    quantity: Decimal = Decimal("0")
    cost_basis_original: Decimal = Decimal("0")
    cost_basis_chf: Decimal = Decimal("0")
    realized_pnl_chf: Decimal = Decimal("0")

    @property
    def average_cost_original(self) -> Decimal | None:
        if self.quantity == 0:
            return None
        return (self.cost_basis_original / self.quantity).quantize(QTY, rounding=ROUND_HALF_UP).normalize()

    @property
    def average_cost_chf(self) -> Decimal | None:
        if self.quantity == 0:
            return None
        return (self.cost_basis_chf / self.quantity).quantize(QTY, rounding=ROUND_HALF_UP).normalize()

    def buy(self, quantity: Decimal, cost_original: Decimal, cost_chf: Decimal) -> None:
        if quantity <= 0:
            raise ValueError("buy quantity must be > 0")
        self.quantity += quantity
        self.cost_basis_original += cost_original
        self.cost_basis_chf += cost_chf

    def sell(self, quantity: Decimal, proceeds_chf: Decimal) -> None:
        if quantity <= 0:
            raise ValueError("sell quantity must be > 0")
        if self.quantity <= 0 or quantity > self.quantity:
            raise ValueError("sell quantity exceeds current position")
        ratio = quantity / self.quantity
        removed_original = self.cost_basis_original * ratio
        removed_chf = self.cost_basis_chf * ratio
        self.quantity -= quantity
        self.cost_basis_original -= removed_original
        self.cost_basis_chf -= removed_chf
        self.realized_pnl_chf += proceeds_chf - removed_chf
        if self.quantity == 0:
            self.cost_basis_original = Decimal("0")
            self.cost_basis_chf = Decimal("0")


@dataclass
class FifoLot:
    activity_id: str
    quantity_opened: Decimal
    quantity_remaining: Decimal
    cost_basis_base: Decimal


@dataclass(frozen=True)
class FifoDisposal:
    sale_activity_id: str
    lot_activity_id: str
    quantity: Decimal
    allocated_cost_basis: Decimal


@dataclass
class FifoResult:
    lots: list[FifoLot] = field(default_factory=list)
    disposals: list[FifoDisposal] = field(default_factory=list)
    realized_pnl: Decimal = Decimal("0")

    @property
    def remaining_quantity(self) -> Decimal:
        return sum((lot.quantity_remaining for lot in self.lots), Decimal("0"))

    @property
    def remaining_cost_basis(self) -> Decimal:
        return sum((lot.cost_basis_base for lot in self.lots), Decimal("0"))


def fifo_v1(
    events: list[dict[str, object]],
) -> FifoResult:
    """Apply FIFO to normalized buy/sell events in deterministic input order.

    Every event needs ``activity_id``, ``kind``, ``quantity`` and base-currency
    ``gross``, ``fee`` and ``tax`` Decimals. Buy fees/taxes increase basis;
    sell fees/taxes reduce proceeds. Unsupported or incomplete events must be
    rejected by the caller before invoking this pure function.
    """

    result = FifoResult()
    for event in events:
        kind = str(event["kind"])
        activity_id = str(event["activity_id"])
        quantity = Decimal(event["quantity"])
        gross = Decimal(event["gross"])
        fee = Decimal(event.get("fee", Decimal("0")))
        tax = Decimal(event.get("tax", Decimal("0")))
        if quantity <= 0:
            raise ValueError("Menge muss grösser als null sein")
        if kind == "buy":
            basis = gross + fee + tax
            result.lots.append(FifoLot(activity_id, quantity, quantity, basis))
            continue
        if kind != "sell":
            continue
        available = result.remaining_quantity
        if quantity > available:
            raise ValueError("Verkauf überschreitet den verfügbaren FIFO-Bestand")
        remaining = quantity
        allocated = Decimal("0")
        for lot in result.lots:
            if remaining == 0:
                break
            if lot.quantity_remaining == 0:
                continue
            consumed = min(remaining, lot.quantity_remaining)
            lot_basis_before = lot.cost_basis_base
            lot_qty_before = lot.quantity_remaining
            consumed_basis = lot_basis_before * consumed / lot_qty_before
            lot.quantity_remaining -= consumed
            lot.cost_basis_base -= consumed_basis
            if lot.quantity_remaining == 0:
                lot.cost_basis_base = Decimal("0")
            allocated += consumed_basis
            result.disposals.append(FifoDisposal(activity_id, lot.activity_id, consumed, consumed_basis))
            remaining -= consumed
        result.realized_pnl += gross - fee - tax - allocated
    return result
