# -*- coding: utf-8 -*-
import os
from pathlib import Path

import numpy as np
from bokeh.layouts import column
from bokeh.layouts import row
from bokeh.models import Button
from bokeh.models import ColumnDataSource
from bokeh.models import Div
from bokeh.models import Select
from bokeh.models import TextInput
from bokeh.plotting import curdoc

try:
    from env import Environment  # type: ignore[import]
except ImportError:
    from iechelle.gui.env import Environment

from iechelle.gui import functions
from iechelle.gui.file_opener import FileOpener


class Settings:
    def __init__(self, env):
        self.env = env
        self._build_widgets()
        self._build_layout()

    def _build_widgets(self):
        self.env.settings_file_type_select = Select(
            title="PSD File Type",
            value=getattr(self.env, "source_file_type", "fits-image"),
            options=["fits-image", "fits-table", "csv", "txt"],
            width=220,
        )
        self.env.settings_frequency_column_text = TextInput(
            value=str(getattr(self.env, "source_frequency_column", "0")),
            title="Frequency column/header",
            width=220,
        )
        self.env.settings_power_column_text = TextInput(
            value=str(getattr(self.env, "source_power_column", "1")),
            title="Power column/header",
            width=220,
        )
        self.env.settings_fits_hdu_text = TextInput(
            value=str(getattr(self.env, "source_fits_hdu", "0")),
            title="FITS HDU (index or name)",
            width=220,
        )
        self.env.settings_text_delimiter_select = Select(
            title="CSV/TXT delimiter",
            value=getattr(self.env, "source_text_delimiter", "auto"),
            options=["auto", "comma", "tab", "space", "semicolon", "pipe"],
            width=220,
        )
        self.env.settings_interface_mode_select = Select(
            title="Interface mode",
            value=getattr(self.env, "interface_mode", "desktop"),
            options=["desktop", "headless/docker"],
            width=220,
        )
        self.env.settings_dialog_engine_select = Select(
            title="Dialog engine",
            value=getattr(self.env, "dialog_engine", "tkinter"),
            options=["tkinter", "pyqt5", "pyqt6", "osascript"],
            width=220,
        )
        self.env.settings_dialog_engine_select.on_change(
            "value", self._on_dialog_engine_change
        )

        self.env.settings_manual_psd_path = TextInput(
            value=getattr(self.env.selected_filename_text, "text", ""),
            title="PSD path",
            width=900,
        )
        self.env.settings_manual_pkb_path = TextInput(
            value=getattr(self.env.selected_filename_pkb_text, "text", ""),
            title="PKB path",
            width=900,
        )
        self.env.settings_manual_bkg_path = TextInput(
            value=getattr(
                self.env.selected_filename_background_text, "text", ""
            ),
            title="BKG path",
            width=900,
        )

        self.env.settings_select_psd_file_button = Button(
            label="Select PSD File",
            button_type=self.env.button_type,
            width=150,
        )
        self.env.settings_select_psd_file_button.on_click(self.select_psd_file)
        self._load_psd_button = Button(
            label="Load PSD",
            button_type=self.env.button_type,
            width=110,
        )
        self._load_psd_button.on_click(self.load_psd_only)
        self._psd_status_div = Div(text="-", width=80, height=30)

        self.env.settings_select_pkb_file_button = Button(
            label="Select PKB File",
            button_type=self.env.button_type,
            width=150,
        )
        self.env.settings_select_pkb_file_button.on_click(self.select_pkb_file)
        self._load_pkb_button = Button(
            label="Load PKB",
            button_type=self.env.button_type,
            width=110,
        )
        self._load_pkb_button.on_click(self.load_pkb_only)
        self._pkb_status_div = Div(text="-", width=80, height=30)

        self.env.settings_select_bkg_file_button = Button(
            label="Select BKG File",
            button_type=self.env.button_type,
            width=150,
        )
        self.env.settings_select_bkg_file_button.on_click(self.select_bkg_file)
        self._load_bkg_button = Button(
            label="Load BKG",
            button_type=self.env.button_type,
            width=110,
        )
        self._load_bkg_button.on_click(self.load_bkg_only)
        self._bkg_status_div = Div(text="-", width=80, height=30)

        # Add change handlers for auto-apply
        self.env.settings_file_type_select.on_change("value", self._auto_load)
        self.env.settings_frequency_column_text.on_change(
            "value", self._auto_load
        )
        self.env.settings_power_column_text.on_change("value", self._auto_load)
        self.env.settings_fits_hdu_text.on_change("value", self._auto_load)
        self.env.settings_text_delimiter_select.on_change(
            "value", self._auto_load
        )
        self.env.settings_manual_psd_path.on_change("value", self._auto_load)
        self.env.settings_manual_pkb_path.on_change("value", self._auto_load)
        self.env.settings_manual_bkg_path.on_change("value", self._auto_load)

        self.env.settings_input_frequency_unit_select = Select(
            title="Input Frequency Units",
            value=self.env.source_input_frequency_unit,
            options=["Hz", "uHz", "mHz", "kHz", "MHz", "GHz", "1/d"],
            width=150,
        )
        self.env.settings_input_frequency_unit_select.on_change(
            "value", self._auto_load
        )

        self.env.settings_target_frequency_unit_select = Select(
            title="Target Frequency Units",
            value=self.env.source_target_frequency_unit,
            options=["Hz", "uHz", "mHz", "kHz", "MHz", "GHz", "1/d"],
            width=150,
        )
        self.env.settings_target_frequency_unit_select.on_change(
            "value", self._auto_load
        )

        self.env.settings_source_info = Div(
            text="<b>Source info:</b> no file selected",
            width=900,
            height=60,
        )
        self._status_div = Div(
            text="PSD: - | PKB: - | BKG: -",
            width=900,
            height=30,
        )

    def _build_layout(self):
        instructions = Div(
            text=(
                "<h3>Settings</h3>"
                "<p>Set file type + column/header mapping. Settings are applied automatically when changed. "
                "If PSD loading fails, edit headers/columns to retry.</p>"
            ),
            width=900,
        )

        self.env.settings_layout = column(
            instructions,
            row(
                self.env.settings_file_type_select,
                self.env.settings_frequency_column_text,
                self.env.settings_power_column_text,
                self.env.settings_fits_hdu_text,
            ),
            row(
                self.env.settings_text_delimiter_select,
                self.env.settings_input_frequency_unit_select,
                self.env.settings_target_frequency_unit_select,
                self.env.settings_interface_mode_select,
                self.env.settings_dialog_engine_select,
            ),
            row(
                self.env.settings_manual_psd_path,
                self.env.settings_select_psd_file_button,
                self._load_psd_button,
                self._psd_status_div,
            ),
            row(
                self.env.settings_manual_pkb_path,
                self.env.settings_select_pkb_file_button,
                self._load_pkb_button,
                self._pkb_status_div,
            ),
            row(
                self.env.settings_manual_bkg_path,
                self.env.settings_select_bkg_file_button,
                self._load_bkg_button,
                self._bkg_status_div,
            ),
            self._status_div,
            self.env.settings_source_info,
            self.env.message_banner,
            sizing_mode="stretch_width",
        )

    def _message(self, text):
        if self.env.message_banner is not None:
            from bokeh.plotting import curdoc

            # Safely update UI from callback
            def update():
                self.env.message_banner.text = text

            curdoc().add_next_tick_callback(update)

    def _next_tick(self, callback):
        curdoc().add_next_tick_callback(callback)

    def _sync_source_settings(self):
        self.env.source_file_type = self.env.settings_file_type_select.value
        self.env.source_frequency_column = (
            self.env.settings_frequency_column_text.value.strip()
        )
        self.env.source_power_column = (
            self.env.settings_power_column_text.value.strip()
        )
        self.env.source_fits_hdu = (
            self.env.settings_fits_hdu_text.value.strip()
        )
        self.env.source_text_delimiter = (
            self.env.settings_text_delimiter_select.value
        )
        self.env.source_input_frequency_unit = (
            self.env.settings_input_frequency_unit_select.value
        )
        self.env.source_target_frequency_unit = (
            self.env.settings_target_frequency_unit_select.value
        )
        self.env.interface_mode = self.env.settings_interface_mode_select.value
        self.env.dialog_engine = self.env.settings_dialog_engine_select.value

    def _on_dialog_engine_change(self, attr, old, new):
        del attr, old
        self.env.dialog_engine = new
        self._message(f"Dialog engine set to: {new}")

    def _can_open_dialog(self):
        return (
            self.env.settings_interface_mode_select.value != "headless/docker"
        )

    def _filetypes_for_psd(self):
        file_type = self.env.settings_file_type_select.value
        mapping = {
            "fits-image": [
                ("FITS Files", "*.fits *.fit"),
                ("All Files", "*.*"),
            ],
            "fits-table": [
                ("FITS Files", "*.fits *.fit"),
                ("All Files", "*.*"),
            ],
            "csv": [("CSV Files", "*.csv"), ("All Files", "*.*")],
            "txt": [("Text Files", "*.txt *.dat"), ("All Files", "*.*")],
        }
        return mapping.get(file_type, [("All Files", "*.*")])

    def _auto_load(self, attr, old, new):
        """Callback for automatic loading when settings change."""
        del attr, old, new
        self.load_input_files()

    def load_input_files(self):
        """Single entry point to load PSD/PKB/BKG and validate each one."""
        self._sync_source_settings()

        psd_path = self.env.settings_manual_psd_path.value.strip()
        pkb_path = self.env.settings_manual_pkb_path.value.strip()
        bkg_path = self.env.settings_manual_bkg_path.value.strip()

        self._next_tick(
            lambda: self._apply_loaded_paths(psd_path, pkb_path, bkg_path)
        )

    def select_psd_file(self):
        if not self._can_open_dialog():
            self._message("Headless/docker mode: paste PSD path manually")
            return
        self._select_file_with_engine(
            file_kind="psd",
            title="Select PSD File",
            filetypes=self._filetypes_for_psd(),
        )

    def select_pkb_file(self):
        if not self._can_open_dialog():
            self._message("Headless/docker mode: paste PKB path manually")
            return
        self._select_file_with_engine(
            file_kind="pkb",
            title="Select PKB File",
            filetypes=[
                ("PKB Files", "*.pkb *.csv *.txt"),
                ("All Files", "*.*"),
            ],
        )

    def select_bkg_file(self):
        if not self._can_open_dialog():
            self._message("Headless/docker mode: paste BKG path manually")
            return
        self._select_file_with_engine(
            file_kind="bkg",
            title="Select BKG File",
            filetypes=[("All Files", "*.*")],
        )

    def _select_file_with_engine(self, file_kind, title, filetypes):
        """Open file dialog using currently selected engine and update path fields."""
        engine = self.env.settings_dialog_engine_select.value
        opener = FileOpener(default_engine=engine)
        file_path = opener.get_open_filename(title=title, filetypes=filetypes)
        if file_path:
            abs_path = os.path.abspath(file_path)
            self._set_path_and_message(file_kind, abs_path)
            return
        self._message(f"{file_kind.upper()} selection cancelled")

    def _set_path_and_message(self, file_kind, abs_path):
        self._set_manual_path(file_kind, abs_path)
        self._message(f"{file_kind.upper()} selected: {abs_path}")

    def _set_manual_path(self, file_kind, file_path):
        abs_path = os.path.abspath(file_path)
        if file_kind == "psd":
            self.env.settings_manual_psd_path.value = abs_path
            self.env.selected_filename_text.text = abs_path
            self.env.selected_filename_fits_text.text = abs_path
        elif file_kind == "pkb":
            self.env.settings_manual_pkb_path.value = abs_path
            self.env.selected_filename_pkb_text.text = abs_path
        elif file_kind == "bkg":
            self.env.settings_manual_bkg_path.value = abs_path
            self.env.selected_filename_background_text.text = abs_path

    def _apply_loaded_paths(self, psd_path, pkb_path, bkg_path):
        if psd_path:
            self.env.settings_manual_psd_path.value = os.path.abspath(psd_path)
        if pkb_path:
            self.env.settings_manual_pkb_path.value = os.path.abspath(pkb_path)
        if bkg_path:
            self.env.settings_manual_bkg_path.value = os.path.abspath(bkg_path)

        psd_ok = self._apply_psd_if_valid(
            self.env.settings_manual_psd_path.value.strip()
        )
        pkb_ok = self._apply_path_only(
            self.env.settings_manual_pkb_path.value.strip(), "pkb"
        )
        bkg_ok = self._apply_path_only(
            self.env.settings_manual_bkg_path.value.strip(), "bkg"
        )

        if pkb_ok:
            pkb_ok = self._load_pkb_into_table2()

        psd_mark = "OK" if psd_ok else "FAIL"
        pkb_mark = "OK" if pkb_ok else "FAIL"
        bkg_mark = "OK" if bkg_ok else "FAIL"
        self._status_div.text = (
            f"PSD: {psd_mark} | PKB: {pkb_mark} | BKG: {bkg_mark}"
        )
        self._psd_status_div.text = psd_mark
        self._pkb_status_div.text = pkb_mark
        self._bkg_status_div.text = bkg_mark

        if psd_ok:
            self._message("Input files loaded. PSD mapping is valid.")
        else:
            self._message(
                "PSD load failed. Update frequency/power/header settings and click 'Load Input Files' again."
            )

    def load_psd_only(self):
        self._sync_source_settings()
        psd_ok = self._apply_psd_if_valid(
            self.env.settings_manual_psd_path.value.strip()
        )
        self._psd_status_div.text = "OK" if psd_ok else "FAIL"
        if psd_ok:
            self._message("PSD loaded successfully")
        else:
            self._message("PSD load failed. Check mapping and path")

    def load_pkb_only(self):
        pkb_ok = self._apply_path_only(
            self.env.settings_manual_pkb_path.value.strip(), "pkb"
        )
        if pkb_ok:
            pkb_ok = self._load_pkb_into_table2()
        self._pkb_status_div.text = "OK" if pkb_ok else "FAIL"
        if pkb_ok:
            self._message("PKB loaded successfully into Table 2")
        else:
            self._message("PKB load failed. Check path")

    def _load_pkb_into_table2(self):
        controller = getattr(self.env, "mode_selection_controller", None)
        if controller is None:
            return False
        try:
            # This method already supports loading from the settings path.
            controller.load_from_file()
            return True
        except Exception as error:  # pylint: disable=broad-except
            self._message(f"PKB load failed while updating Table 2: {error}")
            return False

    def load_bkg_only(self):
        bkg_ok = self._apply_path_only(
            self.env.settings_manual_bkg_path.value.strip(), "bkg"
        )
        self._bkg_status_div.text = "OK" if bkg_ok else "FAIL"
        if bkg_ok:
            self._message("BKG loaded successfully")
        else:
            self._message("BKG load failed. Check path")

    def _apply_psd_if_valid(self, file_path):
        if not file_path:
            return False
        if not Path(file_path).is_file():
            self.env.settings_source_info.text = (
                "<b>Source info:</b> PSD file not found"
            )
            return False

        try:
            ff, pp = functions.read_frequency_power(
                file_path=file_path,
                file_type=self.env.source_file_type,
                frequency_selector=self.env.source_frequency_column,
                power_selector=self.env.source_power_column,
                fits_hdu=self.env.source_fits_hdu,
                text_delimiter=self.env.source_text_delimiter,
                input_units=self.env.source_input_frequency_unit,
                target_units=self.env.source_target_frequency_unit,
            )
            if len(ff) == 0 or len(pp) == 0:
                raise ValueError("Empty frequency/power arrays")

            self._set_psd_path(file_path)
            info = functions.inspect_source_file(
                file_path=file_path,
                file_type=self.env.source_file_type,
                fits_hdu=self.env.source_fits_hdu,
                text_delimiter=self.env.source_text_delimiter,
            )
            self.env.settings_source_info.text = f"<b>Source info:</b> {info}"
            return True
        except Exception as error:  # pylint: disable=broad-except
            self.env.settings_source_info.text = f"<b>Source info:</b> {error}"
            return False

    def _apply_path_only(self, file_path, file_kind):
        if not file_path:
            return False
        abs_path = os.path.abspath(file_path)
        if not Path(abs_path).is_file():
            return False

        if file_kind == "pkb":
            self.env.selected_filename_pkb_text.text = abs_path
        elif file_kind == "bkg":
            self.env.selected_filename_background_text.text = abs_path
        return True

    def _set_psd_path(self, file_path):
        abs_path = os.path.abspath(file_path)
        self.env.selected_filename_text.text = abs_path
        self.env.selected_filename_fits_text.text = abs_path

        id_mycatalog_all = [Path(abs_path).stem]
        id_all = np.arange(0, len(id_mycatalog_all))
        new_data = ColumnDataSource(
            data=dict(
                id_all=list(id_all),
                id_mycatalog_all=id_mycatalog_all,
                path_fits=[abs_path],
                id=[0],
                id_mycatalog=[id_mycatalog_all[0]],
                data_folder=[str(Path(abs_path).parent)],
            )
        )
        self.env.tb_source.data = dict(new_data.data)
