import os
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional, Tuple


@dataclass(frozen=True)
class TimeWindow:
    start_h: int
    start_m: int
    end_h: int
    end_m: int

    def start_minutes(self) -> int:
        return self.start_h * 60 + self.start_m

    def end_minutes(self) -> int:
        return self.end_h * 60 + self.end_m


def _env_str(name: str, default: str) -> str:
    raw = os.getenv(name)
    if raw is None:
        return default
    s = str(raw).strip()
    return s if s else default


def _parse_hhmm(value: str, default: Tuple[int, int]) -> Tuple[int, int]:
    try:
        s = (value or "").strip()
        if not s:
            return default
        parts = s.split(":")
        if len(parts) != 2:
            return default
        h = int(parts[0])
        m = int(parts[1])
        if h < 0 or h > 23 or m < 0 or m > 59:
            return default
        return h, m
    except Exception:
        return default


def market_tz_name() -> str:
    return _env_str("MARKET_TZ", "Asia/Kolkata")


def backend_market_window() -> TimeWindow:
    sh, sm = _parse_hhmm(_env_str("BACKEND_MARKET_START", "09:00"), (9, 0))
    eh, em = _parse_hhmm(_env_str("BACKEND_MARKET_END", "15:30"), (15, 30))
    return TimeWindow(start_h=sh, start_m=sm, end_h=eh, end_m=em)


def paper_trade_window() -> TimeWindow:
    sh, sm = _parse_hhmm(_env_str("PAPER_TRADE_START", "09:30"), (9, 30))
    eh, em = _parse_hhmm(_env_str("PAPER_TRADE_END", "15:00"), (15, 0))
    return TimeWindow(start_h=sh, start_m=sm, end_h=eh, end_m=em)


def _to_local(now_utc: Optional[datetime], tz_name: str) -> datetime:
    if now_utc is None:
        now_utc = datetime.utcnow()

    # Treat incoming as UTC-naive.
    try:
        from zoneinfo import ZoneInfo

        utc = ZoneInfo("UTC")
        tz = ZoneInfo(tz_name)
        return now_utc.replace(tzinfo=None).replace(tzinfo=utc).astimezone(tz)
    except Exception:
        # Best-effort fallback: treat naive as already-local.
        return now_utc


def _within_window_minutes(now_minutes: int, window: TimeWindow, *, include_end: bool) -> bool:
    start = window.start_minutes()
    end = window.end_minutes()
    if start <= end:
        if include_end:
            return start <= now_minutes <= end
        return start <= now_minutes < end

    # If misconfigured to wrap across midnight, treat as: now >= start OR now <= end
    if include_end:
        return now_minutes >= start or now_minutes <= end
    return now_minutes >= start or now_minutes < end


def is_backend_market_hours(now_utc: Optional[datetime] = None) -> bool:
    tz = market_tz_name()
    loc = _to_local(now_utc, tz)

    # Weekday gate: Mon-Fri
    try:
        if loc.weekday() > 4:
            return False
    except Exception:
        pass

    w = backend_market_window()
    mins = int(loc.hour) * 60 + int(loc.minute)
    return _within_window_minutes(mins, w, include_end=True)


def is_paper_trade_creation_time(now_utc: Optional[datetime] = None) -> bool:
    tz = market_tz_name()
    loc = _to_local(now_utc, tz)
    w = paper_trade_window()
    mins = int(loc.hour) * 60 + int(loc.minute)
    # End is exclusive: do NOT create at/after PAPER_TRADE_END
    return _within_window_minutes(mins, w, include_end=False)


def is_paper_trade_end_reached(now_utc: Optional[datetime] = None) -> bool:
    tz = market_tz_name()
    loc = _to_local(now_utc, tz)
    w = paper_trade_window()
    mins = int(loc.hour) * 60 + int(loc.minute)
    return mins >= w.end_minutes()


def format_window(window: TimeWindow) -> str:
    return f"{window.start_h:02d}:{window.start_m:02d}-{window.end_h:02d}:{window.end_m:02d}"


def local_time_str(now_utc: Optional[datetime] = None) -> str:
    tz = market_tz_name()
    loc = _to_local(now_utc, tz)
    try:
        return loc.strftime("%Y-%m-%d %H:%M %Z")
    except Exception:
        return str(loc)


def next_backend_market_open_local(now_utc: Optional[datetime] = None) -> Optional[datetime]:
    """Return next backend market open datetime in local market timezone."""
    tz = market_tz_name()
    loc = _to_local(now_utc, tz)
    w = backend_market_window()

    try:
        candidate = loc.replace(hour=w.start_h, minute=w.start_m, second=0, microsecond=0)
    except Exception:
        return None

    if loc >= candidate:
        candidate = candidate + timedelta(days=1)

    # Weekday gate: Mon-Fri
    try:
        while candidate.weekday() > 4:
            candidate = candidate + timedelta(days=1)
    except Exception:
        pass
    return candidate


def local_now(now_utc: Optional[datetime] = None) -> datetime:
    """Return current datetime in market local timezone."""
    tz = market_tz_name()
    return _to_local(now_utc, tz)


def seconds_until(now_local: datetime, target_local: datetime, *, min_seconds: int = 5) -> int:
    try:
        sec = int((target_local - now_local).total_seconds())
    except Exception:
        sec = 0
    return max(int(min_seconds), sec)
