"""
LFMRIWindow - main application window for the unified LF-MRI GUI.

Single nav-bar design: tab buttons on the left.
The native QMenuBar and QTabWidget tab-bar are both hidden; a QToolBar
provides a unified, flat navigation strip.
"""
from __future__ import annotations

import os
import threading

from PySide6.QtCore import Qt, QObject, QSize, QTimer, Signal
from PySide6.QtWidgets import (
    QApplication,
    QButtonGroup,
    QFrame,
    QLabel,
    QMainWindow,
    QMessageBox,
    QPushButton,
    QSizePolicy,
    QStatusBar,
    QTabWidget,
    QToolBar,
    QWidget,
)

from src import i18n, theme
from src.clients.orchestrator_client import OrchestratorClient, OrchestratorError
from src.tabs.fid_tab import FidTab
from src.tabs.scanner_tab import ScannerTab
from src.tabs.scanning_tab import ScanningTab
from src.tabs.seq_interp_tab import SeqInterpTab
from src.tabs.spectroscopy_tab import SpectroscopyTab

_TAB_NAV_KEYS = [
    "tab_nav_scanning",
    "tab_nav_sequence",
    "tab_nav_scanner",
    "tab_nav_spectro",
    "tab_nav_fid",
]

_SPEC_TAB_IDX = 3
_NAV_H = 38


# -- dynamic CSS helpers -------------------------------------------------------

def _nav_bg(dark: bool) -> str:
    return "#0f0f1e" if dark else "#f0f0f8"


def _nav_toolbar_css(dark: bool) -> str:
    bg     = _nav_bg(dark)
    border = "#1e1e38" if dark else "#d0d0e8"
    return (
        f"QToolBar {{ background: {bg}; border: none; "
        f"border-bottom: 1px solid {border}; spacing: 0px; padding: 0px; }}"
    )


def _tab_btn_css(dark: bool) -> str:
    inactive  = "#7777aa" if dark else "#666688"
    active    = "#ffffff" if dark else "#1a1a2e"
    accent    = "#f0c040" if dark else "#c09000"
    hover_bg  = "#17172e" if dark else "#e0e0f4"
    hover_txt = "#aaaacc" if dark else "#333355"
    return f"""
QPushButton {{
    background: transparent;
    color: {inactive};
    border: none;
    border-bottom: 2px solid transparent;
    padding: 0px 20px;
    font-size: 12px;
    min-height: 36px;
}}
QPushButton:checked {{
    color: {active};
    border-bottom: 2px solid {accent};
}}
QPushButton:hover:!checked {{
    color: {hover_txt};
    background: {hover_bg};
}}
"""


def _lang_btn_css(dark: bool) -> str:
    inactive = "#555577" if dark else "#666688"
    active   = "#f0c040" if dark else "#c09000"
    hover    = "#aaaacc" if dark else "#1a1a2e"
    return f"""
QPushButton {{
    background: transparent;
    color: {inactive};
    border: 1px solid transparent;
    border-radius: 3px;
    padding: 0px 7px;
    font-size: 11px;
    font-weight: bold;
    min-height: 22px;
    max-height: 22px;
}}
QPushButton:checked {{
    color: {active};
    border-color: {active};
}}
QPushButton:hover:!checked {{
    color: {hover};
}}
"""


def _mode_btn_css(dark: bool, mode: str) -> str:
    if mode == "real":
        color  = "#2ecc71"
        border = "#27ae60"
    else:
        color  = "#f39c12"
        border = "#e67e00"
    return f"""
QPushButton {{
    background: transparent;
    color: {color};
    border: 1px solid {border};
    border-radius: 3px;
    padding: 0px 10px;
    font-size: 11px;
    font-weight: bold;
    min-height: 22px;
    max-height: 22px;
    letter-spacing: 0.5px;
}}
QPushButton:hover {{
    background: {color}22;
}}
QPushButton:disabled {{
    color: #555577;
    border-color: #333355;
}}
"""


class _ModeSignalBridge(QObject):
    """Carries results of background mode HTTP calls back to the Qt main thread."""

    mode_fetched = Signal(str)  # mode string on successful GET /mode
    mode_set     = Signal(str)  # mode string on successful POST /mode
    mode_error   = Signal(str)  # error message


def _spec_btn_blink_css(dark: bool) -> str:
    active_txt = "#ffffff" if dark else "#1a1a2e"
    accent     = "#f0c040" if dark else "#c09000"
    hover_bg   = "#17172e" if dark else "#e0e0f4"
    return f"""
QPushButton {{
    background: transparent;
    color: #e65100;
    border: none;
    border-bottom: 2px solid #e65100;
    padding: 0px 20px;
    font-size: 12px;
    min-height: 36px;
}}
QPushButton:checked {{
    color: {active_txt};
    border-bottom: 2px solid {accent};
}}
QPushButton:hover:!checked {{
    color: #ff7733;
    background: {hover_bg};
}}
"""


class LFMRIWindow(QMainWindow):
    """Unified LF-MRI application window."""

    def __init__(
        self,
        hw_config_path: str | None = None,
        output_dir: str | None = None,
        seq_file: str | None = None,
        orchestrator_url:  str = "http://localhost:1717",
        seq_interp_url:    str = "http://localhost:7475",
        spectroscopy_url:  str = "http://localhost:8002",
        reconstructor_url: str = "http://localhost:8081",
        spectrometer_url:  str = "http://localhost:1818",
        hw_mode:           str = "plug",
    ) -> None:
        super().__init__()
        self._hw_mode = hw_mode
        self.setWindowTitle(f"LF-MRI System  [{hw_mode.upper()}]")
        self.setMinimumSize(960, 640)

        self._hw_config_path  = hw_config_path
        self._output_dir      = output_dir
        self._orchestrator_url = orchestrator_url.rstrip("/")

        self._seq_tab = SeqInterpTab(
            hw_config_path=hw_config_path,
            output_dir=output_dir,
            seq_interp_url=seq_interp_url,
        )
        self._scanner_tab = ScannerTab(
            hw_config_path=hw_config_path,
            orchestrator_url=orchestrator_url,
            seq_interp_url=seq_interp_url,
            spectroscopy_url=spectroscopy_url,
            reconstructor_url=reconstructor_url,
            spectrometer_url=spectrometer_url,
        )
        self._fid_tab = FidTab(
            hw_config_path=hw_config_path,
            output_dir=output_dir,
            seq_interp_url=seq_interp_url,
        )
        self._scanning_tab = ScanningTab()
        self._scanning_tab.set_orchestrator_url(orchestrator_url)
        self._spectroscopy_tab = SpectroscopyTab(spectroscopy_url=spectroscopy_url)

        self._tabs = QTabWidget()
        self._tabs.tabBar().hide()
        self._tabs.setDocumentMode(True)
        self._tabs.addTab(self._scanning_tab,     i18n.tr(_TAB_NAV_KEYS[0]))
        self._tabs.addTab(self._seq_tab,          i18n.tr(_TAB_NAV_KEYS[1]))
        self._tabs.addTab(self._scanner_tab,      i18n.tr(_TAB_NAV_KEYS[2]))
        self._tabs.addTab(self._spectroscopy_tab, i18n.tr(_TAB_NAV_KEYS[3]))
        self._tabs.addTab(self._fid_tab,          i18n.tr(_TAB_NAV_KEYS[4]))
        self._tabs.currentChanged.connect(self._on_tab_changed)
        self.setCentralWidget(self._tabs)

        self._fid_tab.fid_seq_generated.connect(self._on_fid_generated)
        self._seq_tab.ready_for_scan.connect(self._on_ready_for_scan)
        self._scanning_tab.scan_job_started.connect(self._scanner_tab.attach_job)
        self._scanning_tab.raw_data_ready.connect(self._on_scan_raw_data_ready)
        self._scanner_tab.scan_result_ready.connect(self._spectroscopy_tab.receive_scan_data)

        self._spec_blink_on: bool = False
        self._spec_blink_timer = QTimer(self)
        self._spec_blink_timer.setInterval(700)
        self._spec_blink_timer.timeout.connect(self._tick_spec_blink)

        # Mode selector state — initialised from server_config.json, then
        # confirmed/overridden by a background GET /mode after startup.
        self._current_mode: str = hw_mode
        self._mode_bridge = _ModeSignalBridge(self)

        self.menuBar().hide()
        self._build_nav_bar()

        # Connect mode signals after nav bar is built (_mode_btn exists)
        self._mode_bridge.mode_fetched.connect(self._on_mode_fetched)
        self._mode_bridge.mode_set.connect(self._on_mode_set)
        self._mode_bridge.mode_error.connect(self._on_mode_error)

        self._build_status_bar()
        self._size_and_center()

        # Apply default dark theme to the whole application
        self._apply_theme(theme.is_dark())

        # Fetch current mode from orchestrator once the event loop is running
        QTimer.singleShot(600, self._start_fetch_mode)

        if seq_file and os.path.isfile(seq_file):
            self._seq_tab.load_seq_file(os.path.abspath(seq_file))

    # ------------------------------------------------------------------ #
    #  Nav bar                                                             #
    # ------------------------------------------------------------------ #

    def _build_nav_bar(self) -> None:
        self._nav_toolbar = QToolBar("Navigation", self)
        tb = self._nav_toolbar
        tb.setMovable(False)
        tb.setFloatable(False)
        tb.setIconSize(QSize(0, 0))
        tb.setFixedHeight(_NAV_H)
        self.addToolBar(Qt.TopToolBarArea, tb)

        self._nav_logo_lbl = QLabel("  LF-MRI  ")
        tb.addWidget(self._nav_logo_lbl)

        self._nav_sep1 = _VSep(tb)
        tb.addWidget(self._nav_sep1)

        self._nav_btn_group = QButtonGroup(self)
        self._nav_btn_group.setExclusive(True)
        self._nav_tab_buttons: list[QPushButton] = []

        for i, key in enumerate(_TAB_NAV_KEYS):
            btn = QPushButton(i18n.tr(key))
            btn.setCheckable(True)
            btn.setFixedHeight(_NAV_H)
            btn.setCursor(Qt.PointingHandCursor)
            self._nav_btn_group.addButton(btn, i)
            tb.addWidget(btn)
            self._nav_tab_buttons.append(btn)
            btn.clicked.connect(lambda _checked, idx=i: self._switch_tab(idx))

        self._nav_tab_buttons[0].setChecked(True)

        self._nav_spacer = QWidget()
        self._nav_spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        tb.addWidget(self._nav_spacer)

        # Mode indicator / switcher
        self._mode_btn = QPushButton("…")
        self._mode_btn.setFixedHeight(22)
        self._mode_btn.setCursor(Qt.PointingHandCursor)
        self._mode_btn.setToolTip("Click to switch operating mode")
        self._mode_btn.clicked.connect(self._on_mode_btn_clicked)
        tb.addWidget(self._mode_btn)

        self._nav_sep_mode = _VSep(tb)
        tb.addWidget(self._nav_sep_mode)

        # Language toggle (EN / RU)
        self._lang_btn_group = QButtonGroup(self)
        self._lang_btn_group.setExclusive(True)
        self._btn_lang_en = QPushButton("EN")
        self._btn_lang_en.setCheckable(True)
        self._btn_lang_en.setChecked(True)
        self._btn_lang_en.setCursor(Qt.PointingHandCursor)
        self._btn_lang_ru = QPushButton("RU")
        self._btn_lang_ru.setCheckable(True)
        self._btn_lang_ru.setCursor(Qt.PointingHandCursor)
        self._lang_btn_group.addButton(self._btn_lang_en)
        self._lang_btn_group.addButton(self._btn_lang_ru)
        self._btn_lang_en.clicked.connect(lambda: self._on_language_change("en"))
        self._btn_lang_ru.clicked.connect(lambda: self._on_language_change("ru"))
        tb.addWidget(self._btn_lang_en)
        tb.addWidget(self._btn_lang_ru)

        # Theme toggle (◑ dark / ◐ light) — plain geometric symbols, no emoji variant
        self._nav_sep2 = _VSep(tb)
        tb.addWidget(self._nav_sep2)

        self._theme_btn_group = QButtonGroup(self)
        self._theme_btn_group.setExclusive(True)
        self._btn_theme_dark  = QPushButton("◑")
        self._btn_theme_light = QPushButton("◐")
        self._btn_theme_dark.setCheckable(True)
        self._btn_theme_light.setCheckable(True)
        self._btn_theme_dark.setChecked(True)  # default dark
        self._btn_theme_dark.setToolTip("Dark theme")
        self._btn_theme_light.setToolTip("Light theme")
        self._btn_theme_dark.setCursor(Qt.PointingHandCursor)
        self._btn_theme_light.setCursor(Qt.PointingHandCursor)
        self._theme_btn_group.addButton(self._btn_theme_dark)
        self._theme_btn_group.addButton(self._btn_theme_light)
        self._btn_theme_dark.clicked.connect(lambda: self._on_theme_toggle(True))
        self._btn_theme_light.clicked.connect(lambda: self._on_theme_toggle(False))
        tb.addWidget(self._btn_theme_dark)
        tb.addWidget(self._btn_theme_light)

    # ------------------------------------------------------------------ #
    #  Theme                                                               #
    # ------------------------------------------------------------------ #

    def _on_theme_toggle(self, dark: bool) -> None:
        theme.set_dark(dark)
        self._apply_theme(dark)

    def _apply_theme(self, dark: bool) -> None:
        app = QApplication.instance()
        if app is not None:
            app.setStyleSheet(theme.make_app_stylesheet())

        bg       = _nav_bg(dark)
        border   = "#1e1e38" if dark else "#d0d0e8"
        sep_clr  = "#1e1e38" if dark else "#d0d0e8"
        logo_clr = "#444466" if dark else "#555577"
        tab_css  = _tab_btn_css(dark)
        lang_css = _lang_btn_css(dark)

        self._nav_toolbar.setStyleSheet(_nav_toolbar_css(dark))
        self._nav_sep1.setStyleSheet(f"background: {sep_clr}; border: none;")
        self._nav_sep2.setStyleSheet(f"background: {sep_clr}; border: none;")
        self._nav_sep_mode.setStyleSheet(f"background: {sep_clr}; border: none;")
        self._nav_logo_lbl.setStyleSheet(
            f"color: {logo_clr}; font-weight: bold; font-size: 11px; "
            f"background: {bg}; padding: 0 4px;"
        )
        self._nav_spacer.setStyleSheet(f"background: {bg};")

        for btn in self._nav_tab_buttons:
            btn.setStyleSheet(tab_css)

        self._btn_lang_en.setStyleSheet(lang_css)
        self._btn_lang_ru.setStyleSheet(lang_css)
        self._btn_theme_dark.setStyleSheet(lang_css)
        self._btn_theme_light.setStyleSheet(lang_css)
        self._update_mode_btn()

        sb_bg    = "#0c0c1a" if dark else "#dce0f0"
        sb_color = "#555577" if dark else "#787faa"
        self.statusBar().setStyleSheet(
            f"QStatusBar {{ background: {sb_bg}; color: {sb_color}; font-size: 11px; }}"
        )

        # Propagate to all tabs
        for tab in (
            self._scanning_tab,
            self._seq_tab,
            self._scanner_tab,
            self._spectroscopy_tab,
            self._fid_tab,
        ):
            if hasattr(tab, "apply_theme"):
                tab.apply_theme()

    # ------------------------------------------------------------------ #
    #  Navigation                                                          #
    # ------------------------------------------------------------------ #

    def _switch_tab(self, index: int) -> None:
        self._tabs.setCurrentIndex(index)
        self._nav_tab_buttons[index].setChecked(True)

    def _build_status_bar(self) -> None:
        sb = QStatusBar()
        self.setStatusBar(sb)

        # Permanent mode badge — stored so _refresh_mode_badge() can update it
        self._mode_badge_lbl = QLabel()
        sb.addPermanentWidget(self._mode_badge_lbl)
        self._refresh_mode_badge()
        sb.showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(_TAB_NAV_KEYS[0])}")

    def _refresh_mode_badge(self) -> None:
        """Update the status-bar mode badge to reflect _current_mode."""
        mode  = self._current_mode
        color = "#e65100" if mode == "real" else "#336633"
        self._mode_badge_lbl.setText(f"  {mode.upper()}  ")
        self._mode_badge_lbl.setStyleSheet(
            f"color: {color}; font-weight: bold; font-size: 10px;"
            f" background: {color}22; border: 1px solid {color}55;"
            " border-radius: 3px; padding: 0 4px; margin: 2px 6px;"
        )
        self._mode_badge_lbl.setToolTip(
            "Hardware mode: real scanner connected"
            if mode == "real"
            else "Stub mode: simulated responses, no hardware"
        )

    def _size_and_center(self) -> None:
        screen = QApplication.primaryScreen()
        if screen is not None:
            ag = screen.availableGeometry()
            w = min(1600, max(960, int(ag.width() * 0.92)))
            h = min(940, max(640, int(ag.height() * 0.90)))
            self.resize(w, h)
            self.move(
                ag.x() + (ag.width()  - w) // 2,
                ag.y() + (ag.height() - h) // 2,
            )
        else:
            self.resize(1440, 860)

    def _on_tab_changed(self, index: int) -> None:
        key = _TAB_NAV_KEYS[index] if 0 <= index < len(_TAB_NAV_KEYS) else "-"
        self.statusBar().showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(key)}")
        if 0 <= index < len(self._nav_tab_buttons):
            self._nav_tab_buttons[index].setChecked(True)
        if index == _SPEC_TAB_IDX:
            self._stop_spec_blink()

    def _on_language_change(self, lang: str) -> None:
        i18n.set_language(lang)
        self.retranslate_ui()

    def retranslate_ui(self) -> None:
        for i, key in enumerate(_TAB_NAV_KEYS):
            self._nav_tab_buttons[i].setText(i18n.tr(key))
        self._update_mode_btn()
        cur = self._tabs.currentIndex()
        key = _TAB_NAV_KEYS[cur] if 0 <= cur < len(_TAB_NAV_KEYS) else "-"
        self.statusBar().showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(key)}")
        for tab in (
            self._scanning_tab,
            self._seq_tab,
            self._scanner_tab,
            self._spectroscopy_tab,
            self._fid_tab,
        ):
            if hasattr(tab, "retranslate_ui"):
                tab.retranslate_ui()

    # ------------------------------------------------------------------ #
    #  Cross-tab signals                                                   #
    # ------------------------------------------------------------------ #

    def _on_fid_generated(self, path: str) -> None:
        self._seq_tab.load_seq_file(path)
        self._switch_tab(1)

    def _on_ready_for_scan(self, info: dict) -> None:
        self._scanner_tab.apply_seq_info(info)
        self._scanning_tab.apply_seq_info(info)
        self._switch_tab(2)

    def _on_scan_raw_data_ready(self, json_path: str) -> None:
        self._spectroscopy_tab.receive_scan_data(json_path)
        if self._tabs.currentIndex() != _SPEC_TAB_IDX:
            self._spec_blink_on = False
            self._spec_blink_timer.start()

    # ------------------------------------------------------------------ #
    #  Spectroscopy blink                                                  #
    # ------------------------------------------------------------------ #

    def _tick_spec_blink(self) -> None:
        self._spec_blink_on = not self._spec_blink_on
        btn = self._nav_tab_buttons[_SPEC_TAB_IDX]
        btn.setStyleSheet(
            _spec_btn_blink_css(theme.is_dark())
            if self._spec_blink_on
            else _tab_btn_css(theme.is_dark())
        )

    def _stop_spec_blink(self) -> None:
        self._spec_blink_timer.stop()
        self._nav_tab_buttons[_SPEC_TAB_IDX].setStyleSheet(
            _tab_btn_css(theme.is_dark())
        )

    # ------------------------------------------------------------------ #
    #  Mode selector                                                       #
    # ------------------------------------------------------------------ #

    def _update_mode_btn(self) -> None:
        """Refresh mode button label and colour from _current_mode."""
        text = i18n.tr(f"mode_{self._current_mode}")
        self._mode_btn.setText(text)
        self._mode_btn.setStyleSheet(_mode_btn_css(theme.is_dark(), self._current_mode))

    # -- background fetch (startup) ----------------------------------------

    def _start_fetch_mode(self) -> None:
        threading.Thread(
            target=self._fetch_mode_bg,
            daemon=True,
            name="mode-fetch",
        ).start()

    def _fetch_mode_bg(self) -> None:
        try:
            client = OrchestratorClient(self._orchestrator_url)
            mode = client.get_mode()
            self._mode_bridge.mode_fetched.emit(mode)
        except OrchestratorError:
            pass  # Orchestrator may not be up yet — keep the default label

    # -- slots (main thread) -----------------------------------------------

    def _on_mode_fetched(self, mode: str) -> None:
        self._current_mode = mode
        self._update_mode_btn()
        self._refresh_mode_badge()
        self._scanner_tab.refresh_scenarios()

    def _on_mode_set(self, mode: str) -> None:
        self._current_mode = mode
        self._mode_btn.setEnabled(True)
        self._update_mode_btn()
        self._refresh_mode_badge()
        self.setWindowTitle(f"LF-MRI System  [{mode.upper()}]")
        self._scanner_tab.refresh_scenarios()

    def _on_mode_error(self, msg: str) -> None:
        self._mode_btn.setEnabled(True)
        QMessageBox.warning(
            self,
            i18n.tr("mode_error"),
            msg or "Unknown error — check that the orchestrator is running.",
        )

    # -- click handler (main thread) ---------------------------------------

    def _on_mode_btn_clicked(self) -> None:
        target = "real" if self._current_mode == "plug" else "plug"
        confirm_key = f"mode_confirm_to_{target}"
        msg = i18n.tr(confirm_key)
        if target == "real":
            msg += (
                "\n\n⚠  Switching to REAL mode requires the native spectrometer "
                "to be already running (start.ps1 -Mode real).\n"
                "If it is not running, the first scan will fail with a connection error."
            )
        reply = QMessageBox.question(
            self,
            i18n.tr("mode_confirm_title"),
            msg,
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No,
        )
        if reply != QMessageBox.StandardButton.Yes:
            return
        self._mode_btn.setEnabled(False)
        threading.Thread(
            target=self._set_mode_bg,
            args=(target,),
            daemon=True,
            name="mode-set",
        ).start()

    def _set_mode_bg(self, mode: str) -> None:
        try:
            client = OrchestratorClient(self._orchestrator_url)
            result = client.set_mode(mode)
            self._mode_bridge.mode_set.emit(result)
        except OrchestratorError as exc:
            status = f"  [HTTP {exc.status_code}]" if exc.status_code else ""
            self._mode_bridge.mode_error.emit(f"{exc}{status}")
        except Exception as exc:
            self._mode_bridge.mode_error.emit(f"{type(exc).__name__}: {exc}")


class _VSep(QFrame):
    """Thin vertical separator for the nav toolbar."""

    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.setFrameShape(QFrame.VLine)
        self.setFixedWidth(1)
        self.setStyleSheet("background: #1e1e38; border: none;")
