# app/src/core/system_status.py

from __future__ import annotations

import re
from dataclasses import dataclass
from datetime import datetime
from zoneinfo import ZoneInfo

from src.core.config import load_settings
from src.core.constants import APP_NAME, APP_VERSION
from src.integrations.exchange.runtime_ui import build_runtime_exchange_alerts
from src.integrations.exchange.service import ExchangeService
from src.integrations.exchange.status import build_exchange_error_status
from src.storage.session import check_database_health
from src.trading.journal.service import JournalService


@dataclass(slots=True)
class ComponentStatus:
    name: str
    state: str
    details: str = ""


@dataclass(slots=True)
class SystemSnapshot:
    app_name: str
    app_version: str
    db_label: str
    timezone_name: str
    mode_label: str
    default_symbol: str
    components: list[ComponentStatus]


def _build_exchange_alert_components(
    *,
    default_symbol: str,
) -> list[ComponentStatus]:
    exchange_service = ExchangeService()

    try:
        runtime_status = exchange_service.get_symbol_runtime_status(default_symbol)
    except Exception as exc:
        runtime_status = build_exchange_error_status(exc)

    if not runtime_status.is_available:
        return [
            ComponentStatus(
                name="Биржа",
                state=runtime_status.ui_line,
                details=runtime_status.message,
            )
        ]

    alerts = build_runtime_exchange_alerts(symbol=default_symbol)

    exchange_unavailable_alert = next(
        (
            alert
            for alert in alerts
            if str(alert.get("code") or "") == "EXCHANGE_UNAVAILABLE"
        ),
        None,
    )

    if exchange_unavailable_alert is not None:
        return [
            ComponentStatus(
                name="Биржа",
                state=str(
                    exchange_unavailable_alert.get("ui_line")
                    or exchange_unavailable_alert.get("title")
                    or "⛔️ Биржа недоступна"
                ),
                details=str(exchange_unavailable_alert.get("details") or ""),
            )
        ]

    components: list[ComponentStatus] = [
        ComponentStatus(
            name="Биржа",
            state="🟢",
            details=runtime_status.message,
        )
    ]

    has_account_alert = False

    for alert in alerts:
        code = str(alert.get("code") or "")
        state = str(
            alert.get("ui_line")
            or alert.get("title")
            or "⛔️ Ошибка биржи"
        )

        if code == "AUTH_ERROR":
            has_account_alert = True
            name = "Аккаунт"
        elif code == "TIME_ERROR":
            name = "Время биржи"
        else:
            name = "Биржа"

        components.append(
            ComponentStatus(
                name=name,
                state=state,
                details=str(alert.get("details") or ""),
            )
        )

    if not has_account_alert:
        components.append(
            ComponentStatus(
                name="Аккаунт",
                state="🟢",
            )
        )

    return components


# извлечь короткую версию PostgreSQL из строки health-check
def _extract_postgres_version(raw: str) -> str:
    if not raw:
        return "PostgreSQL"

    match = re.search(r"PostgreSQL\s+(\d+(?:\.\d+)?)", raw)
    if match:
        return f"PostgreSQL {match.group(1)}"

    return "PostgreSQL"


# проверить подключение к БД и вернуть компонент + подпись версии
def _build_database_status() -> tuple[ComponentStatus, str]:
    db_ok, db_message = check_database_health()
    db_label = _extract_postgres_version(db_message)

    if db_ok:
        return (
            ComponentStatus(
                name="База данных",
                state="🟢",
            ),
            db_label,
        )

    return (
        ComponentStatus(
            name="База данных",
            state="🔴 База данных недоступна",
        ),
        db_label,
    )


# проверить доступность журнала событий
def _build_journal_status() -> ComponentStatus:
    ok, _ = JournalService().get_journal_health()

    if ok:
        return ComponentStatus(
            name="Журнал",
            state="🟢",
        )

    return ComponentStatus(
        name="Журнал",
        state="🔴 Журнал недоступен",
    )


# определить runtime-режим по base_url биржи
def get_runtime_mode_key() -> str:
    settings = load_settings()
    return "demo" if "demo" in settings.exchange_base_url.lower() else "live"


# вернуть человекочитаемую подпись runtime-режима
def get_runtime_mode_label() -> str:
    return "DEMO аккаунт" if get_runtime_mode_key() == "demo" else "LIVE аккаунт"


# собрать полный snapshot системного экрана
def get_system_snapshot() -> SystemSnapshot:
    settings = load_settings()

    database_status, db_label = _build_database_status()
    journal_status = _build_journal_status()

    exchange_components = _build_exchange_alert_components(
        default_symbol=settings.default_symbol,
    )

    components = [
        ComponentStatus(name="Приложение", state="🟢"),
        database_status,
        ComponentStatus(name="Telegram", state="🟢"),
        *exchange_components,
        journal_status,
    ]

    return SystemSnapshot(
        app_name=APP_NAME,
        app_version=APP_VERSION,
        db_label=db_label,
        timezone_name=settings.tz,
        mode_label=get_runtime_mode_label(),
        default_symbol=settings.default_symbol,
        components=components,
    )


# определить, есть ли системные предупреждения
def has_system_alerts(snapshot: SystemSnapshot) -> bool:
    return any(component.state != "🟢" for component in snapshot.components)


# отрендерить одну строку компонента системы
def _render_component(component: ComponentStatus) -> str:
    if component.state == "🟢":
        return f"{component.state} {component.name}"

    return component.state


# получить текущее локальное время для подписи обновления
def _now_hhmmss() -> str:
    settings = load_settings()
    tz_name = settings.tz or "UTC"

    try:
        local_dt = datetime.now(ZoneInfo(tz_name))
    except Exception:
        local_dt = datetime.utcnow()

    return local_dt.strftime("%H:%M:%S")


# собрать текст экрана "Система"
def build_system_text(*, include_updated_at: bool = False) -> str:
    snapshot = get_system_snapshot()

    components_block = "\n".join(
        _render_component(component)
        for component in snapshot.components
    )

    text = (
        "<b>🖥️ Система</b>\n"
        f"🔸 <b>{snapshot.mode_label}</b>\n\n"
        f"{components_block}"
    )

    if include_updated_at:
        text += f"\n\n<i>Обновлено: {_now_hhmmss()}</i>"

    return text