from datetime import date

from pydantic import field_validator

from wbportfolio.order_routing.model import Order

from ...model import ExecutionInstruction
from ..base import AdapterOrder

EXECUTION_INSTRUCTION_MAP = {
    ExecutionInstruction.MARKET_ON_CLOSE: "MARKET_ON_CLOSE",
    ExecutionInstruction.GUARANTEED_MARKET_ON_CLOSE: "GUARANTEED_MARKET_ON_CLOSE",
    ExecutionInstruction.GUARANTEED_MARKET_ON_OPEN: "GUARANTEED_MARKET_ON_OPEN",
    ExecutionInstruction.GPW_MARKET_ON_CLOSE: "GPW_MARKET_ON_CLOSE",
    ExecutionInstruction.MARKET_ON_OPEN: "MARKET_ON_OPEN",
    ExecutionInstruction.IN_LINE_WITH_VOLUME: "IN_LINE_WITH_VOLUME",
    ExecutionInstruction.LIMIT_ORDER: "LIMIT_ORDER",
    ExecutionInstruction.VWAP: "VWAP",
    ExecutionInstruction.TWAP: "TWAP",
}
EXECUTION_INSTRUCTION_MAP_INV = {v: k for k, v in EXECUTION_INSTRUCTION_MAP.items()}


class JPMOrder(AdapterOrder):
    assetClass: str  # noqa: N815
    identifier: str  # noqa: N815
    identifierType: str  # noqa: N815
    tradeDate: date  # noqa: N815
    targetWeight: float  # noqa: N815
    sharesToTrade: float | None = None  # noqa: N815
    executionInstruction: str | None = None  # noqa: N815

    @field_validator("executionInstruction", mode="before")
    @classmethod
    def validate_exec_instr(cls, v: str) -> str:
        if isinstance(v, tuple):
            instruction: ExecutionInstruction = v[0]
            parameters: dict = v[1]
            repr = EXECUTION_INSTRUCTION_MAP[instruction]
            if parameters:
                if instruction == ExecutionInstruction.IN_LINE_WITH_VOLUME:
                    repr += f":{parameters['percent']:.0f%}"
                elif instruction == ExecutionInstruction.LIMIT_ORDER:
                    repr += f":{parameters['price']:.1f}"
                    if good_for_date := parameters.get("good_for_date"):
                        repr += f",{good_for_date}"
                elif instruction == ExecutionInstruction.VWAP or instruction == ExecutionInstruction.TWAP:
                    repr += f":{parameters['period']},{parameters['time']}"
            return repr
        return v

    @property
    def execution_instruction(self) -> tuple[ExecutionInstruction, dict]:
        parts = self.executionInstruction.split(":")
        instruction, parameters = parts if len(parts) == 2 else (parts[0], None)
        instruction = EXECUTION_INSTRUCTION_MAP_INV[instruction]
        if parameters:
            values = parameters.split(",")
            parameters = {f"value{i + 1}": v for i, v in enumerate(values)}
        return instruction, parameters

    @field_validator("assetClass", mode="before")
    @classmethod
    def validate_asset_class(cls, v: str) -> str:
        if isinstance(v, Order.AssetType):
            if v not in [Order.AssetType.EQUITY, Order.AssetType.AMERICAN_DEPOSITORY_RECEIPT]:
                raise ValueError("Asset type not supported")
            return "EQUITY"
        return v

    @classmethod
    def from_order(cls, order: Order) -> "AdapterOrder":
        if order.refinitiv_identifier_code:
            identifier_type, identifier = "RIC", order.refinitiv_identifier_code
        elif order.bloomberg_ticker:
            identifier_type, identifier = "BBTICKER", order.bloomberg_ticker
        else:
            identifier_type, identifier = "SEDOL", order.sedol
        attr = {
            "assetClass": order.asset_class,
            "identifierType": identifier_type,
            "identifier": identifier,
            "executionInstruction": (order.execution_instruction, order.execution_instruction_parameters),
            "userElementId": str(order.id),
            "tradeDate": order.trade_date.strftime("%Y-%m-%d"),
        }
        if order.shares:
            attr["sharesToTrade"] = str(order.shares)
        else:
            attr["targetWeight"] = str(order.target_weight * 100)
        return cls.model_validate(**attr)

    def to_order(self) -> Order:
        execution_instruction, execution_parameters = self.execution_instruction
        return Order(
            id=self.userElementId,
            asset_class=Order.AssetType.EQUITY,
            refinitiv_identifier_code=self.identifier if self.identifierType == "RIC" else None,
            bloomberg_ticker=self.identifier if self.identifierType == "BBTICKER" else None,
            sedol=self.identifier if self.identifierType == "SEDOL" else None,
            trade_date=self.tradeDate,
            target_weight=self.targetWeight * 100,
            shares=self.sharesToTrade,
            execution_instruction=execution_instruction,
            execution_instruction_parameters=execution_parameters,
        )
