# ashyterm/ui/dialogs/ai_config_dialog.py

"""AI Assistant configuration dialog."""

import threading
from typing import List, Tuple

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Adw, GLib, GObject, Gtk

from ...settings.manager import SettingsManager
from ...utils.logger import get_logger
from ...utils.translation_utils import _
from ...utils.accessibility import set_label as a11y_label


class AIConfigDialog(Adw.PreferencesWindow):
    """Dialog for configuring AI assistant settings."""

    __gsignals__ = {
        "setting-changed": (GObject.SignalFlags.RUN_FIRST, None, (str, object)),
    }

    # Provider configurations
    PROVIDERS = [
        ("groq", "Groq", "https://api.groq.com/openai/v1"),
        ("gemini", "Gemini", "https://generativelanguage.googleapis.com"),
        ("openrouter", "OpenRouter", "https://openrouter.ai/api/v1"),
        ("cerebras", "Cerebras", "https://api.cerebras.ai/v1"),
        ("github", "GitHub Models", "https://models.inference.ai.azure.com"),
        ("mistral", "Mistral AI", "https://api.mistral.ai/v1"),
        ("local", "Local (Ollama/LM Studio)", "http://localhost:11434/v1"),
    ]

    DEFAULT_MODELS = {
        "groq": "llama-3.1-8b-instant",
        "gemini": "gemini-2.5-flash",
        "openrouter": "openrouter/polaris-alpha",
        "cerebras": "llama-3.3-70b",
        "github": "gpt-4o-mini",
        "mistral": "mistral-small-latest",
        "local": "llama3.2",
    }

    def __init__(self, parent_window, settings_manager: SettingsManager):
        super().__init__(
            title=_("Configure AI Assistant"),
            transient_for=parent_window,
            modal=False,  # Use non-modal like other dialogs to prevent freeze on mapping failure
            default_width=750,
            default_height=600,
            search_enabled=False,
        )
        self.add_css_class("ashyterm-dialog")
        self.logger = get_logger("ashyterm.ui.dialogs.ai_config")
        self.settings_manager = settings_manager

        # Debug: track if dialog maps successfully
        self.connect(
            "map",
            lambda w: self.logger.info(
                "[MAP_DEBUG] AIConfigDialog mapped successfully"
            ),
        )

        self._setup_ui()
        self.logger.info("AI config dialog initialized")

    def _setup_ui(self) -> None:
        """Set up the dialog UI."""
        page = Adw.PreferencesPage(title=_("AI Assistant"))
        self.add(page)

        # Enable/Disable group - MUST be first element user sees
        enable_group = Adw.PreferencesGroup()
        page.add(enable_group)

        self.enable_switch = Adw.SwitchRow(
            title=_("Enable AI Assistant"),
            subtitle=_("Show the AI Assistant button in the header bar."),
        )
        self.enable_switch.set_active(
            self.settings_manager.get("ai_assistant_enabled", False)
        )
        self.enable_switch.connect("notify::active", self._on_enable_changed)
        enable_group.add(self.enable_switch)

        # Provider selection group
        provider_group = Adw.PreferencesGroup()
        page.add(provider_group)

        # Provider combo row
        self.provider_row = Adw.ComboRow(
            title=_("Provider"),
            subtitle=_("Choose between cloud providers or local models."),
        )
        provider_model = Gtk.StringList.new([label for _, label, _ in self.PROVIDERS])
        self.provider_row.set_model(provider_model)

        # Set current provider
        current_provider = self.settings_manager.get("ai_assistant_provider", "groq")
        provider_index = self._get_provider_index(current_provider)
        self.provider_row.set_selected(provider_index)
        self.provider_row.connect("notify::selected", self._on_provider_changed)
        provider_group.add(self.provider_row)

        # Base URL row (for local providers)
        self.base_url_row = Adw.EntryRow(
            title=_("Base URL"),
        )
        self.base_url_row.set_text(
            self.settings_manager.get("ai_local_base_url", "http://localhost:11434/v1")
        )
        self.base_url_row.connect("changed", self._on_base_url_changed)
        provider_group.add(self.base_url_row)

        # API Key group
        api_group = Adw.PreferencesGroup()
        page.add(api_group)

        # API Key row with GET button
        self.api_key_box = Adw.PasswordEntryRow(
            title=_("API Key"),
        )
        self.get_key_button = Gtk.LinkButton(label=_("Get API Key"))
        self.get_key_button.set_valign(Gtk.Align.CENTER)
        self.api_key_box.add_suffix(self.get_key_button)
        self.api_key_box.connect("changed", self._on_api_key_changed)
        api_group.add(self.api_key_box)

        # Model selection group
        model_group = Adw.PreferencesGroup()
        page.add(model_group)

        # Model entry row
        self.model_row = Adw.EntryRow(
            title=_("Model Identifier"),
        )
        self.model_row.connect("changed", self._on_model_changed)
        model_group.add(self.model_row)

        # Browse models button
        self.browse_models_row = Adw.ActionRow(
            title=_("Browse Available Models"),
        )
        self.browse_models_button = Gtk.Button(label=_("Browse Models"))
        self.browse_models_button.set_valign(Gtk.Align.CENTER)
        self.browse_models_button.connect("clicked", self._on_browse_models_clicked)
        self.browse_models_row.add_suffix(self.browse_models_button)
        self.browse_models_row.set_activatable_widget(self.browse_models_button)
        model_group.add(self.browse_models_row)

        # Update UI based on current provider
        self._update_ui_for_provider(current_provider)

    def _get_provider_index(self, provider_id: str) -> int:
        """Get the index of a provider in the PROVIDERS list."""
        for i, (pid, _name, _desc) in enumerate(self.PROVIDERS):
            if pid == provider_id:
                return i
        return 0

    def _get_selected_provider_id(self) -> str:
        """Get the currently selected provider ID."""
        index = self.provider_row.get_selected()
        if 0 <= index < len(self.PROVIDERS):
            return self.PROVIDERS[index][0]
        return "groq"

    def _update_ui_for_provider(self, provider_id: str) -> None:
        """Update UI elements based on the selected provider."""
        is_local = provider_id == "local"
        is_openrouter = provider_id == "openrouter"

        # Show/hide base URL for local provider
        self.base_url_row.set_visible(is_local)

        # Show/hide API key (local may not need it)
        self.api_key_box.set_sensitive(not is_local)

        # Show/hide browse models button (OpenRouter and Local)
        self.browse_models_row.set_visible(True)

        # Update button URLs and titles
        self.get_key_button.set_visible(not is_local)

        if provider_id == "groq":
            self.model_row.set_title(_("Model Identifier"))
            self.api_key_box.set_title(_("Groq API Key"))
            self.get_key_button.set_uri("https://console.groq.com/keys")
            self.browse_models_row.set_subtitle(
                _("Search and select from available Groq models.")
            )
        elif provider_id == "gemini":
            self.model_row.set_title(_("Model Identifier"))
            self.api_key_box.set_title(_("Google AI Studio API Key"))
            self.get_key_button.set_uri("https://aistudio.google.com/app/apikey")
            self.browse_models_row.set_subtitle(
                _("Search and select from available Gemini models.")
            )
        elif provider_id == "openrouter":
            self.model_row.set_title(_("Model Identifier"))
            self.api_key_box.set_title(_("OpenRouter API Key"))
            self.get_key_button.set_uri("https://openrouter.ai/keys")
            self.browse_models_row.set_subtitle(
                _("Search and select from available OpenRouter models.")
            )
        elif provider_id == "cerebras":
            self.model_row.set_title(_("Model Identifier"))
            self.api_key_box.set_title(_("Cerebras API Key"))
            self.get_key_button.set_uri("https://cloud.cerebras.ai/")
            self.browse_models_row.set_subtitle(
                _("Search and select from available Cerebras models.")
            )
        elif provider_id == "github":
            self.model_row.set_title(_("Model Identifier"))
            self.api_key_box.set_title(_("GitHub Personal Access Token"))
            self.get_key_button.set_uri("https://github.com/settings/tokens")
            self.browse_models_row.set_subtitle(
                _("Search and select from available GitHub models.")
            )
        elif provider_id == "mistral":
            self.model_row.set_title(_("Model Identifier"))
            self.api_key_box.set_title(_("Mistral API Key"))
            self.get_key_button.set_uri("https://console.mistral.ai/api-keys")
            self.browse_models_row.set_subtitle(
                _("Search and select from available Mistral models.")
            )
        elif provider_id == "local":
            self.model_row.set_title(_("Model Name"))
            self.api_key_box.set_title(_("API Key (if required)"))
            self.browse_models_row.set_subtitle(
                _("Search and select from available Local models.")
            )

        # Update specific config values
        provider_api_key = self.settings_manager.get(
            f"ai_assistant_{provider_id}_api_key", ""
        )
        if not provider_api_key:
            provider_api_key = self.settings_manager.get("ai_assistant_api_key", "")

        default_model = self.DEFAULT_MODELS.get(provider_id, "")
        provider_model = self.settings_manager.get(
            f"ai_assistant_{provider_id}_model", ""
        )
        if not provider_model:
            provider_model = self.settings_manager.get("ai_assistant_model", "")

        # Unblock signal momentarily to avoid feedback
        self.api_key_box.set_text(provider_api_key)
        self.model_row.set_text(provider_model or default_model)

    def _on_provider_changed(self, combo_row, _param) -> None:
        """Handle provider selection change."""
        provider_id = self._get_selected_provider_id()
        self.settings_manager.set("ai_assistant_provider", provider_id)
        self._update_ui_for_provider(provider_id)
        self.emit("setting-changed", "ai_assistant_provider", provider_id)

    def _on_base_url_changed(self, entry_row) -> None:
        """Handle base URL change."""
        url = entry_row.get_text().strip()
        self.settings_manager.set("ai_local_base_url", url)
        self.emit("setting-changed", "ai_local_base_url", url)

    def _on_api_key_changed(self, entry_row) -> None:
        """Handle API key change."""
        key = entry_row.get_text().strip()
        provider_id = self._get_selected_provider_id()
        self.settings_manager.set(f"ai_assistant_{provider_id}_api_key", key)
        self.emit("setting-changed", "ai_assistant_api_key", key)

    def _on_model_changed(self, entry_row) -> None:
        """Handle model change."""
        model = entry_row.get_text().strip()
        provider_id = self._get_selected_provider_id()
        self.settings_manager.set(f"ai_assistant_{provider_id}_model", model)
        self.emit("setting-changed", "ai_assistant_model", model)

    def _on_enable_changed(self, switch_row, _param) -> None:
        """Handle enable/disable change."""
        enabled = switch_row.get_active()
        self.settings_manager.set("ai_assistant_enabled", enabled)
        self.emit("setting-changed", "ai_assistant_enabled", enabled)

    def _on_browse_models_clicked(self, button) -> None:
        """Open a searchable dialog to browse and select models."""
        provider_id = self._get_selected_provider_id()

        api_key = self.settings_manager.get(
            f"ai_assistant_{provider_id}_api_key", ""
        ).strip()
        if not api_key:
            api_key = self.settings_manager.get("ai_assistant_api_key", "").strip()

        base_url = ""
        if provider_id == "openrouter":
            base_url = "https://openrouter.ai/api/v1"
        elif provider_id == "groq":
            base_url = "https://api.groq.com/openai/v1"
        elif provider_id == "gemini":
            base_url = "https://generativelanguage.googleapis.com/v1beta"
        elif provider_id == "cerebras":
            base_url = "https://api.cerebras.ai/v1"
        elif provider_id == "github":
            base_url = "https://models.inference.ai.azure.com"
        elif provider_id == "mistral":
            base_url = "https://api.mistral.ai/v1"
        elif provider_id == "local":
            base_url = self.settings_manager.get(
                "ai_local_base_url", "http://localhost:11434/v1"
            )

        if provider_id != "local" and not api_key:
            self._show_toast(_("Please enter an API key first."))
            return

        # Open the model browser dialog
        dialog = ModelBrowserDialog(self, provider_id, base_url, api_key)
        dialog.connect("model-selected", self._on_model_browser_selected)
        dialog.present()

    def _on_model_browser_selected(self, dialog, model_id: str) -> None:
        """Handle model selection from the browser dialog."""
        self.model_row.set_text(model_id)
        dialog.close()

    def _show_toast(self, message: str) -> None:
        """Show a toast notification."""
        toast = Adw.Toast(title=message)
        self.add_toast(toast)


class ModelBrowserDialog(Adw.Window):
    """Searchable dialog for browsing and selecting models."""

    __gsignals__ = {
        "model-selected": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
    }

    def __init__(self, parent_window, provider_id: str, base_url: str, api_key: str):
        super().__init__(
            title=_("Select Model"),
            transient_for=parent_window,
            modal=True,
            default_width=800,
            default_height=600,
        )
        self.add_css_class("ashyterm-dialog")
        self.provider_id = provider_id
        self.base_url = base_url
        self.api_key = api_key
        self.logger = get_logger("ashyterm.ui.dialogs.model_browser")
        self._all_models: List[Tuple[str, str, bool]] = []
        self._filtered_models: List[Tuple[str, str, bool]] = []
        self._fetching = False
        self._cancel_requested = False
        self._show_free_only = False

        self._setup_ui()
        self._fetch_models()

    def _setup_ui(self) -> None:
        """Set up the dialog UI."""
        toolbar_view = Adw.ToolbarView()
        self.set_content(toolbar_view)

        # Header bar
        header = Adw.HeaderBar()
        header.set_show_end_title_buttons(True)
        header.set_show_start_title_buttons(False)

        cancel_button = Gtk.Button(label=_("Cancel"))
        cancel_button.connect("clicked", lambda _: self.close())
        header.pack_start(cancel_button)

        toolbar_view.add_top_bar(header)

        # Main content
        main_box = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=12,
            margin_top=12,
            margin_bottom=12,
            margin_start=12,
            margin_end=12,
        )
        toolbar_view.set_content(main_box)

        # Search entry
        # Search row with filter toggle
        search_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        self.search_entry = Gtk.SearchEntry(
            placeholder_text=_("Search models by name or ID..."),
            hexpand=True,
        )
        a11y_label(self.search_entry, _("Search models"))
        self.search_entry.connect("search-changed", self._on_search_changed)
        search_box.append(self.search_entry)

        # Free filter toggle (only for OpenRouter)
        self._free_filter_btn = Gtk.ToggleButton(
            label=_("Free"),
            valign=Gtk.Align.CENTER,
            tooltip_text=_(
                "Show only free models."
            ),
        )
        self._free_filter_btn.add_css_class("flat")
        a11y_label(self._free_filter_btn, _("Show only free models"))
        self._free_filter_btn.connect("toggled", self._on_free_filter_toggled)
        self._free_filter_btn.set_visible(self.provider_id == "openrouter")
        search_box.append(self._free_filter_btn)

        main_box.append(search_box)

        # Status label / spinner
        self.status_box = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=8,
            halign=Gtk.Align.CENTER,
            margin_top=8,
            margin_bottom=8,
        )
        self.spinner = Gtk.Spinner()
        self.status_label = Gtk.Label(label=_("Loading models..."))
        self._cancel_fetch_btn = Gtk.Button(label=_("Cancel"))
        self._cancel_fetch_btn.add_css_class("flat")
        self._cancel_fetch_btn.connect("clicked", self._on_cancel_fetch)
        self.status_box.append(self.spinner)
        self.status_box.append(self.status_label)
        self.status_box.append(self._cancel_fetch_btn)
        main_box.append(self.status_box)

        # Results count label
        self.count_label = Gtk.Label(
            label="",
            css_classes=["dim-label"],
            halign=Gtk.Align.START,
        )
        main_box.append(self.count_label)

        # Scrolled window with model list
        scrolled = Gtk.ScrolledWindow(vexpand=True)
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        scrolled.add_css_class("card")
        main_box.append(scrolled)

        # Model list
        self.list_box = Gtk.ListBox()
        self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
        self.list_box.add_css_class("boxed-list")
        self.list_box.connect("row-activated", self._on_row_activated)
        scrolled.set_child(self.list_box)

    def _fetch_models(self) -> None:
        """Fetch models from OpenRouter API."""
        if self._fetching:
            return

        self._fetching = True
        self._cancel_requested = False
        self.spinner.start()
        self.status_box.set_visible(True)
        self._cancel_fetch_btn.set_visible(True)
        self.count_label.set_visible(False)

        thread = threading.Thread(
            target=self._fetch_models_thread,
            daemon=True,
        )
        thread.start()

    def _fetch_models_thread(self) -> None:
        """Fetch models in a background thread."""
        try:
            import requests

            headers = self._build_auth_headers()
            models_url = self._build_models_url()

            response = requests.get(models_url, headers=headers, timeout=30)

            if self._cancel_requested:
                return

            # Fallback to Ollama native API if /v1/models returns 404
            if response.status_code == 404 and "/v1/models" in models_url:
                models_url = models_url.replace("/v1/models", "/api/tags")
                response = requests.get(models_url, headers=headers, timeout=30)

            if response.status_code >= 400:
                GLib.idle_add(self._on_fetch_error, f"HTTP {response.status_code}")
                return

            model_list = self._parse_models_response(response.json())
            model_list.sort(key=lambda x: x[1].lower())
            GLib.idle_add(self._on_fetch_success, model_list)

        except Exception as e:
            GLib.idle_add(self._on_fetch_error, str(e))

    def _build_auth_headers(self) -> dict:
        """Build HTTP headers with provider-specific auth."""
        headers = {"Content-Type": "application/json"}
        if self.api_key:
            if self.provider_id == "gemini":
                headers["x-goog-api-key"] = self.api_key
            else:
                headers["Authorization"] = f"Bearer {self.api_key}"
        return headers

    def _build_models_url(self) -> str:
        """Build the models API URL with Ollama detection."""
        base_url = self.base_url.rstrip("/")
        if base_url.endswith(":11434"):
            base_url += "/v1"
        return f"{base_url}/models"

    def _parse_models_response(self, data: dict) -> list:
        """Parse model list from API response (handles OpenAI, Ollama, Gemini shapes)."""
        if "models" in data and isinstance(data["models"], list):
            models = data["models"]
        elif "data" in data and isinstance(data["data"], list):
            models = data["data"]
        else:
            models = []

        model_list = []
        for model in models:
            model_id = model.get("id", model.get("name", ""))
            if self.provider_id == "gemini" and model_id.startswith("models/"):
                model_id = model_id[7:]
            model_name = model.get("name", model.get("display_name", model_id))
            is_free = self._is_model_free(model)
            if model_id:
                model_list.append((model_id, model_name, is_free))
        return model_list

    @staticmethod
    def _is_model_free(model: dict) -> bool:
        """Check if a model is free based on pricing data."""
        pricing = model.get("pricing", {})
        if not pricing:
            return False
        try:
            prompt_cost = float(pricing.get("prompt", "1"))
            completion_cost = float(pricing.get("completion", "1"))
            return prompt_cost == 0 and completion_cost == 0
        except (ValueError, TypeError):
            return False

    def _on_fetch_success(self, models: List[Tuple[str, str]]) -> None:
        """Handle successful model fetch."""
        self._fetching = False
        self.spinner.stop()
        self.status_box.set_visible(False)

        self._all_models = models
        self._filtered_models = models
        self._update_model_list()

        # Focus search entry
        self.search_entry.grab_focus()

    def _on_fetch_error(self, error: str) -> None:
        """Handle fetch error."""
        self._fetching = False
        self.spinner.stop()
        self._cancel_fetch_btn.set_visible(False)
        self.status_label.set_text(
            _("Failed to load models: {error}").format(error=error)
        )
        self.status_label.add_css_class("error")

    def _on_cancel_fetch(self, _button) -> None:
        """Cancel the model fetch."""
        self._cancel_requested = True
        self._fetching = False
        self.spinner.stop()
        self._cancel_fetch_btn.set_visible(False)
        self.status_label.set_text(_("Fetch cancelled"))
        self.status_label.remove_css_class("error")

    def _on_free_filter_toggled(self, button) -> None:
        """Toggle free models filter."""
        self._show_free_only = button.get_active()
        self._apply_filters()

    def _on_search_changed(self, search_entry) -> None:
        """Filter models based on search text."""
        self._apply_filters()

    def _apply_filters(self) -> None:
        """Apply search text and free filter to model list."""
        search_text = self.search_entry.get_text().lower().strip()
        result = self._all_models

        if search_text:
            result = [
                m for m in result
                if search_text in m[0].lower() or search_text in m[1].lower()
            ]

        if self._show_free_only:
            result = [m for m in result if m[2]]

        self._filtered_models = result
        self._update_model_list()

    def _update_model_list(self) -> None:
        """Update the model list display."""
        # Clear existing items
        while True:
            row = self.list_box.get_row_at_index(0)
            if row is None:
                break
            self.list_box.remove(row)

        # Update count label
        total = len(self._all_models)
        shown = len(self._filtered_models)
        if total == shown:
            self.count_label.set_text(_("{count} models available").format(count=total))
        else:
            self.count_label.set_text(
                _("Showing {shown} of {total} models").format(shown=shown, total=total)
            )
        self.count_label.set_visible(True)

        # Add filtered models (limit to first 100 for performance)
        for model_id, model_name, is_free in self._filtered_models[:100]:
            row = self._create_model_row(model_id, model_name, is_free)
            self.list_box.append(row)

        if len(self._filtered_models) > 100:
            hint_label = Gtk.Label(
                label=_("Showing first 100 results. Refine your search to see more."),
                css_classes=["dim-label"],
                margin_top=8,
                margin_bottom=8,
            )
            hint_row = Gtk.ListBoxRow(child=hint_label, selectable=False)
            self.list_box.append(hint_row)

    def _create_model_row(self, model_id: str, model_name: str, is_free: bool = False) -> Gtk.ListBoxRow:
        """Create a row for a model."""
        row = Gtk.ListBoxRow()
        row.model_id = model_id  # Store ID for later retrieval

        box = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=4,
            margin_top=8,
            margin_bottom=8,
            margin_start=12,
            margin_end=12,
        )

        # Name row with optional Free badge
        name_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        name_label = Gtk.Label(
            label=model_name,
            xalign=0,
            css_classes=["heading"],
            wrap=True,
            wrap_mode=2,  # WORD_CHAR
            hexpand=True,
        )
        name_box.append(name_label)

        if is_free:
            free_badge = Gtk.Label(
                label=_("Free"),
                css_classes=["success", "caption"],
                valign=Gtk.Align.CENTER,
                tooltip_text=_(
                    "Free model — some providers may log or train on "
                    "prompts. Check OpenRouter privacy settings."
                ),
            )
            name_box.append(free_badge)

        box.append(name_box)

        # Model ID (smaller, dim)
        id_label = Gtk.Label(
            label=model_id,
            xalign=0,
            css_classes=["dim-label", "caption"],
            selectable=True,
        )
        box.append(id_label)

        row.set_child(box)
        return row

    def _on_row_activated(self, list_box, row) -> None:
        """Handle row activation (selection)."""
        if hasattr(row, "model_id"):
            self.emit("model-selected", row.model_id)
