import asyncio
import carb
import omni.kit.app
import omni.kit.usd.layers as layers
import omni.ui as ui
import re

from .layer_icons import LayerIcons
from .utils import join_live_session, is_viewer_only_mode, get_session_list_select, SESSION_LIST_SELECT_DEFAULT_SESSION
from omni.kit.widget.prompt import PromptButtonInfo, PromptManager
from pxr import Sdf
from .live_session_model import LiveSessionModel


class LiveSessionStartWindow:

    def __init__(self, layers_interface: layers.Layers, layer_identifier, prim_path):
        self._window = None
        self._layers_interface = layers_interface
        self._base_layer_identifier = layer_identifier
        self._live_prim_path = prim_path
        self._buttons = []
        self._existing_sessions_combo = None
        self._session_name_input_field = None
        self._session_name_input_hint = None
        self._error_label = None
        self._session_name_begin_edit_cb = None
        self._session_name_end_edit_cb = None
        self._session_name_edit_cb = None
        self._existing_sessions_model = LiveSessionModel(self._layers_interface, layer_identifier)
        self._participants_list = None
        self._participants_layout = None
        self._existing_sessions_model.set_user_update_callback(self._on_channel_users_update)
        self._existing_sessions_model.add_value_changed(self._on_session_list_changed)
        self._update_user_list_task: asyncio.Future = None
        self._join_create_radios = None

        self._key_functions = {
            int(carb.input.KeyboardInput.ENTER): self._on_ok_button_fn,
            int(carb.input.KeyboardInput.ESCAPE): self._on_cancel_button_fn
        }

        self._build_ui()

        def _on_layer_event(event: carb.events.IEvent):
            payload = layers.get_layer_event_payload(event)
            if payload.event_type == layers.LayerEventType.LIVE_SESSION_LIST_CHANGED:
                if self._base_layer_identifier not in payload.identifiers_or_spec_paths:
                    return

                self._existing_sessions_model.refresh_sessions(force=True)
                self._update_dialog_states()

        self._layers_event_subscription = layers_interface.get_event_stream().create_subscription_to_pop(
            _on_layer_event, name="Session Start Window Events"
        )

    def destroy(self):
        self._layers_interface = None
        for button in self._buttons:
            button.set_clicked_fn(None)
        self._buttons.clear()
        self._participants_list = None
        if self._window:
            self._window.visible = False
        self._window = None
        if self._existing_sessions_model:
            self._existing_sessions_model.set_user_update_callback(None)
            self._existing_sessions_model.destroy()
            self._existing_sessions_model = None

        self._participants_layout = None
        self._layers_event_subscription = None
        self._existing_sessions_combo = None
        self._existing_sessions_empty_hint = None
        self._join_create_radios = None
        self._session_name_input_hint = None
        self._session_name_input_field = None
        self._session_name_begin_edit_cb = None
        self._session_name_end_edit_cb = None
        self._session_name_edit_cb = None
        self._error_label = None
        if self._update_user_list_task:
            try:
                self._update_user_list_task.cancel()
            except Exception:
                pass
        self._update_user_list_task = None

    @property
    def visible(self):
        return self._window and self._window.visible

    @visible.setter
    def visible(self, value):
        if self._window:
            self._window.visible = value

        if not self._window.visible:
            if self._update_user_list_task:
                try:
                    self._update_user_list_task.cancel()
                except Exception:
                    pass
            self._update_user_list_task = None
            self._existing_sessions_model.stop_channel()

    def set_focus(self, join_session):
        if not self._join_create_radios:
            return

        if join_session:
            self._join_create_radios.model.set_value(0)
            if get_session_list_select() == SESSION_LIST_SELECT_DEFAULT_SESSION:
                self._existing_sessions_model.select_default_session()
        else:
            self._join_create_radios.model.set_value(1)

            async def async_focus_keyboard():
                await omni.kit.app.get_app().next_update_async()
                self._session_name_input_field.focus_keyboard()

            omni.kit.async_engine.run_coroutine(async_focus_keyboard())

    def _on_channel_users_update(self):
        async def _update_users():
            all_users = self._existing_sessions_model.all_users
            current_session = self._existing_sessions_model.current_session
            if not current_session:
                return

            self._participants_list.clear()
            with self._participants_list:
                if len(all_users) > 0:
                    for _, user in all_users.items():
                        is_owner = current_session.owner == user.user_name
                        with ui.VStack(height=0):
                            ui.Spacer(height=5)
                            with ui.HStack(height=0):
                                ui.Spacer(width=10)
                                if is_owner:
                                    ui.Label(f"{user.user_name} - {user.from_app} (owner)", style={"color": 0xFF808080})
                                else:
                                    ui.Label(f"{user.user_name} - {user.from_app}", style={"color": 0xFF808080})
                else:
                    self._build_empty_participants_list()

        if not self._update_user_list_task or self._update_user_list_task.done():
            self._update_user_list_task = asyncio.ensure_future(_update_users())

    def _validate_session_name(self, str):
        if re.match(r'^[a-zA-Z][a-zA-Z0-9-_]*$', str):
            return True

        return False

    def _on_join_session(self, current_session):
        layer_identifier = self._base_layer_identifier
        layers_interface = self._layers_interface
        prim_path = self._live_prim_path
        return join_live_session(
            layers_interface, layer_identifier, current_session, prim_path, is_viewer_only_mode()
        )

    def _on_ok_button_fn(self): # pragma: no cover
        live_syncing = self._layers_interface.get_live_syncing()
        current_option = self._join_create_radios.model.as_int
        join_session = current_option == 0
        if join_session:
            current_session = self._existing_sessions_model.current_session
            if not current_session:
                self._error_label.text = "No Valid Session Selected"
                self._error_label.visible = True
            elif self._on_join_session(current_session):
                self._error_label.text = ""
                self._error_label.visible = False
                self.visible = False
            else:
                self._error_label.text = "Failed to join session, please check console for more details."
                self._error_label.visible = True
        else:
            session_name = self._session_name_input_field.model.get_value_as_string()
            session_name = session_name.strip()
            if not session_name or not self._validate_session_name(session_name):
                self._update_button_status(session_name)
                self._error_label.text = "Session name must be given."
                self._error_label.visible = True
            else:
                session = live_syncing.create_live_session(layer_identifier=self._base_layer_identifier, name=session_name)
                if not session:
                    self._error_label.text = "Failed to create session, please check console for more details."
                    self._error_label.visible = True
                elif not self._on_join_session(session):
                    self._error_label.text = "Failed to join session, please check console for more details."
                    self._error_label.visible = True
                else:
                    self._error_label.text = ""
                    self._error_label.visible = False
                    self.visible = False

    def _on_cancel_button_fn(self):
        self.visible = False

    def _on_key_pressed_fn(self, key, mod, pressed):
        if not pressed:
            return

        func = self._key_functions.get(key)
        if func:
            func()

    def _build_option_checkbox(self, name, text, default_value, tooltip=""): # pragma: no cover
        with ui.HStack(height=0, width=0):
            checkbox = ui.CheckBox(width=20, name=name)
            checkbox.model.set_value(default_value)
            label = ui.Label(text, alignment=ui.Alignment.LEFT)
            if tooltip:
                label.set_tooltip(tooltip)

            return checkbox, label

    def _build_option_radio(self, collection, name, text, tooltip=""):
        style = {
            "": {"background_color": 0x0, "image_url": LayerIcons.get("radio_off")},
            ":checked": {"image_url": LayerIcons.get("radio_on")},
        }

        with ui.HStack(height=0, width=0):
            radio = ui.RadioButton(radio_collection=collection, width=28, height=28, name=name, style=style)
            ui.Spacer(width=4)
            label = ui.Label(text, alignment=ui.Alignment.LEFT_CENTER)
            if tooltip:
                label.set_tooltip(tooltip)

        return radio

    def _update_button_status(self, session_name):
        join_button = self._buttons[0]
        if session_name and not self._validate_session_name(session_name):
            if not str.isalpha(session_name[0]):
                self._error_label.text = "Session name must be prefixed with letters."
            else:
                self._error_label.text = "Only alphanumeric letters, hyphens, or underscores are supported."
            self._error_label.visible = True
            join_button.enabled = False
        else:
            self._error_label.text = ""
            self._error_label.visible = False
            join_button.enabled = True

    def _on_session_name_begin_edit(self, model):
        self._session_name_input_hint.visible = False
        session_name = model.get_value_as_string().strip()
        self._update_button_status(session_name)

    def _on_session_name_end_edit(self, model):
        if len(model.get_value_as_string()) == 0:
            self._session_name_input_hint.visible = True

        session_name = model.get_value_as_string().strip()
        self._update_button_status(session_name)

    def _on_session_name_edit(self, model):
        session_name = model.get_value_as_string().strip()
        self._update_button_status(session_name)

    def _update_dialog_states(self):
        self._error_label.text = ""
        current_option = self._join_create_radios.model.as_int
        show_join_session = current_option == 0
        join_button = self._buttons[0]
        if show_join_session:
            self._existing_sessions_combo.visible = True
            self._session_name_input_field.visible = False
            self._session_name_input_hint.visible = False
            self._participants_layout.visible = True
            self._existing_sessions_model.refresh_sessions()
            if self._existing_sessions_model.empty():
                self._existing_sessions_empty_hint.visible = True
            else:
                self._existing_sessions_empty_hint.visible = False
            join_button.text = "JOIN"
        else:
            self._participants_layout.visible = False
            self._existing_sessions_combo.visible = False
            self._session_name_input_field.visible = True
            self._session_name_input_hint.visible = False
            self._existing_sessions_empty_hint.visible = False
            self._existing_sessions_model.clear()
            join_button.text = "CREATE"
            new_session_name = self._existing_sessions_model.create_new_session_name()
            self._session_name_input_field.model.set_value(new_session_name)
            self._session_name_input_field.focus_keyboard()

    def _on_radios_changed_fn(self, model):
        self._update_dialog_states()

        self._on_checkbox_changed_called = False

    def _on_session_list_changed(self, model):
        if self._participants_list:
            self._participants_list.clear()

    def _build_ui(self):
        """Construct the window based on the current parameters"""
        self._window = ui.Window("Live Session", visible=False, height=0, auto_resize=True, dockPreference=ui.DockPreference.DISABLED)
        self._window.flags = (
            ui.WINDOW_FLAGS_NO_COLLAPSE | ui.WINDOW_FLAGS_NO_SCROLLBAR |
            ui.WINDOW_FLAGS_NO_RESIZE | ui.WINDOW_FLAGS_NO_MOVE
        )
        self._window.set_key_pressed_fn(self._on_key_pressed_fn)
        self._window.flags = self._window.flags | ui.WINDOW_FLAGS_MODAL

        self._existing_sessions_model.refresh_sessions()
        empty_sessions = self._existing_sessions_model.empty()

        with self._window.frame:
            with ui.VStack(height=0):
                ui.Spacer(width=0, height=10)
                with ui.HStack(height=0):
                    ui.Spacer()
                    self._join_create_radios = ui.RadioCollection()
                    self._build_option_radio(self._join_create_radios, "join_session_radio_button", "Join Session")
                    ui.Spacer(width=20)
                    self._build_option_radio(self._join_create_radios, "create_session_radio_button", "Create Session")
                    ui.Spacer()
                    self._join_create_radios.model.add_value_changed_fn(lambda _: self._update_dialog_states())
                ui.Spacer(width=0, height=15)
                with ui.HStack(height=0):
                    ui.Spacer(width=20)
                    self._error_label = ui.Label("", name="error_label", style={"color": 0xFF0000CC}, alignment=ui.Alignment.LEFT, word_wrap=True)
                    ui.Spacer(width=20)
                ui.Spacer(width=0, height=5)
                with ui.ZStack(height=0):
                    with ui.HStack(height=0):
                        ui.Spacer(width=20)
                        with ui.ZStack(height=0):
                            self._session_name_input_field = ui.StringField(
                                name="new_session_name_field", width=self._window.width - 40, height=0
                            )
                            self._session_name_input_hint = ui.Label(
                                " New Session Name", alignment=ui.Alignment.LEFT_CENTER, style={"color": 0xFF3F3F3F}
                            )
                            self._session_name_begin_edit_cb = self._session_name_input_field.model.subscribe_begin_edit_fn(
                                self._on_session_name_begin_edit
                            )
                            self._session_name_end_edit_cb = self._session_name_input_field.model.subscribe_end_edit_fn(
                                self._on_session_name_end_edit
                            )
                            self._session_name_edit_cb = self._session_name_input_field.model.subscribe_value_changed_fn(
                                self._on_session_name_edit
                            )
                        ui.Spacer(width=20)
                    with ui.HStack(height=0):
                        ui.Spacer(width=20)
                        with ui.ZStack(height=0):
                            self._existing_sessions_combo = ui.ComboBox(
                                self._existing_sessions_model, width=self._window.width - 40, height=0
                            )
                            self._existing_sessions_empty_hint = ui.Label(
                                " No Existing Sessions", alignment=ui.Alignment.LEFT_CENTER, style={"color": 0xFF3F3F3F}
                            )
                        ui.Spacer(width=20)
                ui.Spacer(width=0, height=10)
                self._participants_layout = ui.VStack(height=0)
                with self._participants_layout:
                    with ui.HStack(height=0):
                        ui.Spacer(width=20)
                        with ui.VStack(height=0, width=0):
                            with ui.HStack(height=0, width=0):
                                ui.Label("Participants:   ", alignment=ui.Alignment.CENTER)
                            with ui.HStack():
                                ui.Spacer()
                                ui.Image(LayerIcons.get("participants"), width=28, height=28)
                                ui.Spacer()
                        ui.Spacer(width=0, height=5)
                        with ui.HStack(height=0):
                            with ui.ScrollingFrame(height=120, style={"background_color": 0xFF24211F}):
                                self._participants_list = ui.VStack(height=0)
                                with self._participants_list:
                                    self._build_empty_participants_list()
                        ui.Spacer(width=20)
                ui.Spacer(width=0, height=30)
                with ui.HStack(height=0):
                    ui.Spacer(height=0)
                    ok_button = ui.Button("JOIN", name="confirm_button", width=120, height=0)
                    ok_button.set_clicked_fn(self._on_ok_button_fn)
                    cancel_button = ui.Button("CANCEL", name="cancel_button", width=120, height=0)
                    cancel_button.set_clicked_fn(self._on_cancel_button_fn)
                    self._buttons.append(ok_button)
                    self._buttons.append(cancel_button)
                    ui.Spacer(height=0, width=20)
                ui.Spacer(width=0, height=20)

        if empty_sessions:
            self._join_create_radios.model.set_value(1)
        else:
            self._update_dialog_states()

    def _build_empty_participants_list(self):
        with ui.VStack(height=0):
            ui.Spacer(height=5)
            with ui.HStack(height=0):
                ui.Spacer(width=10)
                ui.Label("No users currently in session.", style={"color": 0xFF808080})
