from uuid import UUID, uuid4
from alpaca.trading.enums import OrderSide, OrderStatus, TradeEvent,OrderType
#from utils import AttributeDict
from rich import print
from typing import Any, Optional, List, Union
from datetime import datetime, date
from pydantic import BaseModel, Field
from v2realbot.enums.enums import Mode, Account, SchedulerStatus, Moddus, Market
from alpaca.data.enums import Exchange




#models for server side datatables
# Model for individual column data
class ColumnData(BaseModel):
    data: str
    name: str
    searchable: bool
    orderable: bool
    search: dict

# Model for the search value
class SearchValue(BaseModel):
    value: str
    regex: bool

class OrderValue(BaseModel):
    column: int
    dir: str

# Model for incoming DataTables request
class DataTablesRequest(BaseModel):
    draw: int
    start: int
    length: int
    search: SearchValue
    order: List[OrderValue]
    columns: List[ColumnData]

#tu samou variantu pak UpdateStrategyInstanceWhileRunning

#only those that can be changed UUID id prijde v parametru
# @app.put("/api/v1/users/{id}")
# async def update_user(user_update: UpdateUser, id: UUID):
#  for user in db:
#  if user.id == id:
#  if user_update.first_name is not None:
#  user.first_name = user_update.first_name
#  if user_update.last_name is not None:
#  userbase.last_name = user_update.last_name
#  if user_update.roles is not None:
#  user.roles = user_update.roles
#  return user.id
#  raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}")


#obecny vstup pro analyzera (vstupem muze byt bud batch_id nebo seznam runneru)
class AnalyzerInputs(BaseModel):
    function: str
    batch_id: Optional[str] = None
    runner_ids: Optional[List[UUID]] = None
    #additional parameter
    params: Optional[dict] = {}

class RunDay(BaseModel):
    """
    Helper object for batch run - carries list of days in format required by run batch manager
    """
    start: datetime
    end: datetime
    name: Optional[str] = None
    note: Optional[str] = None
    id: Optional[str] = None

# Define a Pydantic model for input data
class ConfigItem(BaseModel):
    id: Optional[int] = None
    item_name: str
    json_data: str

class Intervals(BaseModel):
    start: str
    end: str
    note: Optional[str] = None

# Define the data model for the TestLists
class TestList(BaseModel):
    id: Optional[UUID | str | None] = None
    name: str
    dates: List[Intervals]

#for GUI to fetch historical trades on given symbol
class Trade(BaseModel):
    symbol: str
    timestamp: datetime
    exchange: Optional[Union[Exchange, str]] = None
    price: float
    size: float
    id: int
    conditions: Optional[List[str]] = None
    tape: Optional[str] = None


#persisted object in pickle
class StrategyInstance(BaseModel):
    id: Optional[UUID | str | None] = None
    id2: int
    name: str
    symbol: str
    class_name: str
    script: str
    open_rush: int = 0
    close_rush: int = 0
    stratvars_conf: str
    add_data_conf: str
    note: Optional[str]  = None
    history: Optional[str] = None

    def __setstate__(self, state: dict[Any, Any]) -> None:
        """
        Hack to allow unpickling models stored from pydantic V1
        """
        state.setdefault("__pydantic_extra__", {})
        state.setdefault("__pydantic_private__", {})

        if "__pydantic_fields_set__" not in state:
            state["__pydantic_fields_set__"] = state.get("__fields_set__")

        super().__setstate__(state)

class RunRequest(BaseModel):
    id: UUID
    account: Account
    mode: Mode
    note: Optional[str] = None
    debug: bool = False
    strat_json: Optional[str] = None
    ilog_save: bool = False
    bt_from: Optional[datetime] = None
    bt_to: Optional[datetime] = None
    #weekdays filter
    #pokud je uvedeny filtrujeme tyto dny
    weekdays_filter: Optional[list] = None
    #id testovaciho intervalu TODO prejmenovat
    test_batch_id: Optional[str] = None
    #GENERATED ID v ramci runu, vaze vsechny runnery v batchovem behu
    batch_id: Optional[str] = None
    cash: int = 100000
    skip_cache: Optional[bool] = False

#Trida, která je nadstavbou runrequestu a pouzivame ji v scheduleru, je zde navic jen par polí
class RunManagerRecord(BaseModel):
    moddus: Moddus
    id: UUID = Field(default_factory=uuid4) 
    strat_id: UUID
    symbol: Optional[str] = None
    account: Account
    mode: Mode
    note: Optional[str] = None
    ilog_save: bool = False
    market: Optional[Market] = Market.US
    bt_from: Optional[datetime] = None
    bt_to: Optional[datetime] = None
    #weekdays filter
    #pokud je uvedeny filtrujeme tyto dny
    weekdays_filter: Optional[list] = None #list of strings 0-6 representing days to run
    #GENERATED ID v ramci runu, vaze vsechny runnery v batchovem behu
    batch_id: Optional[str] = None
    testlist_id: Optional[str] = None
    start_time: str #time (HH:MM) that start function is called
    stop_time: Optional[str] = None #time  (HH:MM) that stop function is called
    status: SchedulerStatus
    last_processed: Optional[datetime] = None
    history: Optional[str] = None
    valid_from: Optional[datetime] = None # US East time zone daetime
    valid_to: Optional[datetime] = None # US East time zone daetime
    runner_id: Optional[UUID] = None #last runner_id from scheduler after stratefy is started
    strat_running: Optional[bool] = None #automatically updated field based on status of runner_id above, it is added by row_to_RunManagerRecord
class RunnerView(BaseModel):
    id: UUID
    strat_id: UUID
    run_started: Optional[datetime] = None
    run_mode: Mode
    run_name: Optional[str] = None
    run_note: Optional[str] = None
    run_account: Account
    run_ilog_save: Optional[bool] = False
    run_symbol: Optional[str] = None
    run_trade_count: Optional[int] = 0
    run_profit: Optional[float] = 0
    run_positions: Optional[int] = 0
    run_avgp: Optional[float] = 0
    run_stopped: Optional[datetime] = None
    run_paused: Optional[datetime] = None    
 
#Running instance - not persisted
class Runner(BaseModel):
    id: UUID
    strat_id: UUID
    batch_id: Optional[str] = None
    run_started: Optional[datetime] = None
    run_mode: Mode
    run_account: Account
    run_symbol: Optional[str] = None
    run_name: Optional[str] = None
    run_note: Optional[str] = None
    run_ilog_save: Optional[bool] = False
    run_trade_count: Optional[int] = None
    run_profit: Optional[float] = None
    run_positions: Optional[int] = None
    run_avgp: Optional[float] = None
    run_strat_json: Optional[str] = None
    run_stopped: Optional[datetime] = None
    run_paused: Optional[datetime] = None   
    run_thread: Optional[object] = None
    run_instance: Optional[object] = None
    run_pause_ev: Optional[object] = None
    run_stop_ev: Optional[object] = None
    run_stratvars_toml: Optional[str] = None


class Bar(BaseModel):
    """Represents one bar/candlestick of aggregated trade data over a specified interval.

    Attributes:
        symbol (str): The ticker identifier for the security whose data forms the bar.
        timestamp (datetime): The closing timestamp of the bar.
        open (float): The opening price of the interval.
        high (float): The high price during the interval.
        low (float): The low price during the interval.
        close (float): The closing price of the interval.
        volume (float): The volume traded over the interval.
        trade_count (Optional[float]): The number of trades that occurred.
        vwap (Optional[float]): The volume weighted average price.
        exchange (Optional[float]): The exchange the bar was formed on.
    """

    symbol: str
    timestamp: datetime
    open: float
    high: float
    low: float
    close: float
    volume: float
    trade_count: Optional[float] = 0
    vwap: Optional[float] = 0

class Order(BaseModel):
    id: UUID
    submitted_at: datetime
    filled_at: Optional[datetime] = None
    canceled_at: Optional[datetime] = None
    symbol: str
    qty: int
    status: OrderStatus
    order_type: OrderType
    filled_qty: Optional[int] = None
    filled_avg_price: Optional[float] = None
    side: OrderSide
    limit_price: Optional[float] = None

#entita pro kazdy kompletni FILL, je navazana na prescribed_trade 
class TradeUpdate(BaseModel):
    event: Union[TradeEvent, str]
    execution_id: Optional[UUID] = None
    order: Order
    timestamp: datetime
    position_qty: Optional[float] = None
    price: Optional[float] = None
    qty: Optional[float] = None
    value: Optional[float] = None
    cash: Optional[float] = None
    pos_avg_price: Optional[float] = None
    profit: Optional[float] = None
    profit_sum: Optional[float] = None
    rel_profit: Optional[float] = None
    rel_profit_cum: Optional[float] = None
    signal_name: Optional[str] = None
    prescribed_trade_id: Optional[str] = None


class RunArchiveChange(BaseModel):
    id: UUID
    note: str

#do budoucna pouzit SQLAlchemy
#Contains archive of running strategies (runner) - master
class RunArchive(BaseModel):
    #unique id of algorun
    id: UUID
    #id of running strategy (stratin/runner)
    strat_id: UUID
    batch_id: Optional[str] = None
    symbol: str
    name: str
    note: Optional[str] = None
    started: datetime
    stopped: Optional[datetime] = None
    mode: Mode
    account: Account
    bt_from: Optional[datetime] = None
    bt_to: Optional[datetime] = None
    strat_json: Optional[str] = None
    transferables: Optional[dict] = None #varaibles that are transferrable to next run
    settings: Optional[dict] = None
    ilog_save: Optional[bool] = False
    profit: float = 0
    trade_count: int = 0
    end_positions: int = 0
    end_positions_avgp: float = 0
    metrics: Union[dict, str] = None
    stratvars_toml: Optional[str] = None

#For gui view master table
class RunArchiveView(BaseModel):
    id: UUID
    strat_id: UUID
    batch_id: Optional[str] = None
    symbol: str
    name: str
    note: Optional[str] = None
    started: datetime
    stopped: Optional[datetime] = None
    mode: Mode
    account: Account
    bt_from: Optional[datetime] = None
    bt_to: Optional[datetime] = None
    ilog_save: Optional[bool] = False
    profit: float = 0
    trade_count: int = 0
    end_positions: int = 0
    end_positions_avgp: float = 0
    metrics: Union[dict, str] = None
    batch_profit: float = 0  # Total profit for the batch - now calculated during query
    batch_count: int = 0  # Count of runs in the batch - now calculated during query

#same but with pagination
class RunArchiveViewPagination(BaseModel):
    draw: int
    recordsTotal: int
    recordsFiltered: int
    data: List[RunArchiveView]

#trida pro ukladani historie stoplossy do ext_data
class SLHistory(BaseModel):
    id: Optional[UUID] = None
    time: datetime
    sl_val: float

#Contains archive of running strategies (runner) - detail data
class RunArchiveDetail(BaseModel):
    id: UUID
    name: str
    bars: dict
    #trades: Optional[dict]
    indicators: List[dict]
    statinds: dict
    trades: List[TradeUpdate]
    ext_data: Optional[dict] = None
    

class InstantIndicator(BaseModel):
    name: str
    toml: str


