import re

from datetime import datetime, timezone

from pydantic import BaseModel, Field, field_validator
from zoneinfo import ZoneInfo

_HHMM = re.compile(r"^\d{1,2}:\d{2}$")


def _normalize_hhmm(value: str) -> str:
    raw = value.strip()
    if not _HHMM.match(raw):
        raise ValueError("Expected format HH:MM (e.g. 09:00)")
    hour, minute = raw.split(":")
    h, m = int(hour), int(minute)
    if h > 23 or m > 59:
        raise ValueError("Invalid time")
    return f"{h:02d}:{m:02d}"


class TradeZone(BaseModel):
    id: str
    low: float
    high: float = Field(gt=0)
    instrument: str | None = None
    label: str | None = None
    side: str = ""
    direction: str = ""
    source: str = "addon"

    @field_validator("low")
    @classmethod
    def validate_low(cls, value: float) -> float:
        if value <= 0:
            raise ValueError("Invalid low price")
        return value


class CompanionRulesSettings(BaseModel):
    discipline_engaged: bool = False
    discipline_engaged_at: str | None = None
    session_enabled: bool = True
    session_timezone: str = "Europe/Paris"
    session_start: str = "09:00"
    session_end: str = "18:00"

    max_trades_enabled: bool = True
    max_trades_per_session: int = Field(default=5, ge=1, le=999)

    max_risk_per_trade_enabled: bool = True
    max_risk_usd: float = Field(default=100.0, gt=0)

    daily_loss_limit_enabled: bool = True
    max_daily_loss_pct: float = Field(default=3.0, gt=0, le=100)
    paper_balance: float = Field(default=2000.0, gt=0)

    max_lot_enabled: bool = False
    max_quantity: float = Field(default=1.0, gt=0)

    trade_interval_enabled: bool = False
    trade_interval_mode: str = "after_loss"
    trade_interval_minutes: int = Field(default=15, ge=1, le=240)

    trade_zones_enabled: bool = False
    trade_zones: list[TradeZone] = Field(default_factory=list)
    zones_edit_lock_minutes: int = Field(default=60, ge=0, le=480)

    usage_start: str = "09:00"
    usage_end: str = "22:00"

    exit_discipline_enabled: bool = False
    exit_mode: str = "manual_or_sl"
    sl_modify_limit_enabled: bool = True
    sl_modify_max_count: int = Field(default=0, ge=0, le=50)
    sl_breakeven_only_enabled: bool = False

    @field_validator("exit_mode")
    @classmethod
    def validate_exit_mode(cls, value: str) -> str:
        mode = value.strip().lower()
        if mode not in ("manual_or_sl", "sl_only"):
            raise ValueError("Invalid exit mode (manual_or_sl or sl_only)")
        return mode

    @field_validator("trade_interval_mode")
    @classmethod
    def validate_interval_mode(cls, value: str) -> str:
        mode = value.strip().lower()
        if mode not in ("after_loss", "always"):
            raise ValueError("Invalid interval mode (after_loss or always)")
        return mode

    @field_validator("session_start", "session_end", "usage_start", "usage_end")
    @classmethod
    def validate_time(cls, value: str) -> str:
        return _normalize_hhmm(value)

    @field_validator("session_timezone")
    @classmethod
    def validate_timezone(cls, value: str) -> str:
        tz = value.strip()
        try:
            ZoneInfo(tz)
        except Exception as exc:
            raise ValueError(f"Invalid IANA timezone: {tz}") from exc
        return tz


class CompanionRulesUpdate(BaseModel):
    discipline_engaged: bool | None = None
    session_enabled: bool | None = None
    session_timezone: str | None = None
    session_start: str | None = None
    session_end: str | None = None

    max_trades_enabled: bool | None = None
    max_trades_per_session: int | None = Field(default=None, ge=1, le=999)

    max_risk_per_trade_enabled: bool | None = None
    max_risk_usd: float | None = Field(default=None, gt=0)
    max_risk_per_trade_pct: float | None = Field(default=None, gt=0, le=100)

    daily_loss_limit_enabled: bool | None = None
    max_daily_loss_pct: float | None = Field(default=None, gt=0, le=100)
    paper_balance: float | None = Field(default=None, gt=0)

    max_lot_enabled: bool | None = None
    max_quantity: float | None = Field(default=None, gt=0)

    trade_interval_enabled: bool | None = None
    trade_interval_mode: str | None = None
    trade_interval_minutes: int | None = Field(default=None, ge=1, le=240)

    trade_zones_enabled: bool | None = None
    trade_zones: list[TradeZone] | None = None
    zones_edit_lock_minutes: int | None = Field(default=None, ge=0, le=480)

    usage_start: str | None = None
    usage_end: str | None = None

    exit_discipline_enabled: bool | None = None
    exit_mode: str | None = None
    sl_modify_limit_enabled: bool | None = None
    sl_modify_max_count: int | None = Field(default=None, ge=0, le=50)
    sl_breakeven_only_enabled: bool | None = None

    @field_validator("exit_mode")
    @classmethod
    def validate_exit_mode(cls, value: str | None) -> str | None:
        if value is None:
            return None
        mode = value.strip().lower()
        if mode not in ("manual_or_sl", "sl_only"):
            raise ValueError("Invalid exit mode (manual_or_sl or sl_only)")
        return mode

    @field_validator("trade_interval_mode")
    @classmethod
    def validate_interval_mode(cls, value: str | None) -> str | None:
        if value is None:
            return None
        mode = value.strip().lower()
        if mode not in ("after_loss", "always"):
            raise ValueError("Invalid interval mode (after_loss or always)")
        return mode

    @field_validator("session_start", "session_end", "usage_start", "usage_end")
    @classmethod
    def validate_time(cls, value: str | None) -> str | None:
        if value is None:
            return None
        return _normalize_hhmm(value)

    @field_validator("session_timezone")
    @classmethod
    def validate_timezone(cls, value: str | None) -> str | None:
        if value is None:
            return None
        tz = value.strip()
        try:
            ZoneInfo(tz)
        except Exception as exc:
            raise ValueError(f"Invalid IANA timezone: {tz}") from exc
        return tz
