from datetime import date, datetime
from typing import Optional

from pydantic import BaseModel, Field, 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):
    Identifier: str  # noqa: N815
    Units: float  # noqa: N815
    UnitType: str  # noqa: N815
    ExecutionType: str  # noqa: N815
    StartTimeWap: str | None = None  # noqa: N815
    EndTimeWap: str | None = None  # noqa: N815
    Pov: str | None = None  # noqa: N815

    @classmethod
    def get_market_instruction_attrs(cls, order: Order) -> dict:
        attrs = {}
        if order.execution_instruction == ExecutionInstruction.TWAP:
            attrs["ExecutionType"] = "TWAP"
            attrs["StartTimeWap"] = order.execution_instruction_parameters["start"]
            attrs["EndTimeWap"] = order.execution_instruction_parameters["end"]

        elif order.execution_instruction == ExecutionInstruction.VWAP:
            attrs["ExecutionType"] = "VWAP"
            attrs["StartTimeWap"] = order.execution_instruction_parameters["start"]
            attrs["EndTimeWap"] = order.execution_instruction_parameters["end"]
        elif order.execution_instruction == ExecutionInstruction.IN_LINE_WITH_VOLUME:
            attrs["ExecutionType"] = "POV"
            attrs["Pov"] = order.execution_instruction_parameters["percent"]
        elif order.execution_instruction == ExecutionInstruction.MARKET_ON_CLOSE:
            attrs["ExecutionType"] = "MOC"
        else:
            raise ValueError(f"Unsupported market type execution for JPM: {order.execution_instruction}")
        return attrs

    @classmethod
    def from_order(cls, order: Order, identifier_type: str = "ticker") -> "AdapterOrder":
        attr = {}
        if identifier_type == "ticker" and order.ticker:
            attr["Identifier"] = order.ticker
        elif identifier_type == "isin" and order.isin:
            attr["Identifier"] = order.isin
        elif identifier_type == "refinitiv_identifier_code" and order.refinitiv_identifier_code:
            attr["Identifier"] = order.refinitiv_identifier_code
        elif identifier_type == "sedol" and order.sedol:
            attr["Identifier"] = order.sedol
        else:
            raise ValueError(f"Unsupported identifier type for JPM: {identifier_type}")

        attr.update(cls.get_market_instruction_attrs(order))

        if order.shares:
            attr["Units"] = str(order.shares)
            attr["UnitType"] = "SHARES"
        else:
            attr["Units"] = str(order.target_weight)
            attr["UnitType"] = "WEIGHT"
        return cls.model_validate(attr)

    def to_order(self, rebalancing_date: date) -> Order:
        return Order(
            id="NA",  # JPM does not allow setting custom id so we let handler find it from the identifier
            asset_class=Order.AssetType.EQUITY,
            ticker=self.Identifier,
            trade_date=rebalancing_date,
            target_weight=self.Units if self.UnitType == "WEIGHT" else None,
            shares=self.Units if self.UnitType == "SHARES" else None,
        )


class RebalancingRequestOrder(BaseModel):
    date: date
    name: str
    isin: str
    previous_weight: float = Field(alias="previousweight")
    previous_quantity: int = Field(alias="previousquantity")
    target_weight: float = Field(alias="targetweight")
    target_quantity: int = Field(alias="targetquantity")
    requested_datetime: datetime = Field(alias="requesteddatetime")
    execution_type: str = Field(alias="executiontype")
    side: str
    execution_start_datetime: Optional[datetime] = Field(default=None, alias="executionstartdatetime")
    execution_end_datetime: Optional[datetime] = Field(default=None, alias="executionenddatetime")
    filled_price: float = Field(alias="filledprice")
    currency_of_price: str = Field(alias="currencyofprice")
    exchange: str
    fees: float

    @field_validator("date", mode="before")
    @classmethod
    def parse_date_dd_mm_yyyy(cls, v):
        if isinstance(v, str):
            return datetime.strptime(v, "%d-%m-%Y").date()
        return v

    @field_validator("requested_datetime", mode="before")
    @classmethod
    def parse_requested_datetime(cls, v):
        if isinstance(v, str):
            return datetime.strptime(v, "%d-%m-%Y %H:%M")
        return v

    @field_validator("execution_start_datetime", "execution_end_datetime", mode="before")
    @classmethod
    def empty_string_to_none(cls, v):
        return None if v == "" else v

    def to_order(self):
        execution_instruction = {
            "MOC": ExecutionInstruction.MARKET_ON_CLOSE,
            "VWAP": ExecutionInstruction.VWAP,
            "TWAP": ExecutionInstruction.TWAP,
            "POV": ExecutionInstruction.IN_LINE_WITH_VOLUME,
        }[self.execution_type]

        return Order(
            id="NA",  # JPM does not allow setting custom id so we let handler find it from the identifier
            asset_class=Order.AssetType.EQUITY,
            isin=self.isin,
            trade_date=self.date,
            target_weight=self.target_weight,
            target_shares=self.target_quantity,
            execution_price=self.filled_price,
            execution_instruction=execution_instruction,
        )
