#!/usr/bin/env python3
"""
Locales tab for SolydXK System

Author: Arjen Balfoort
Date: 2026-02-22
"""

import os
import re
import queue
import json
import tempfile
import gettext
from typing import Optional
import apt

from localize import Localize
from utils import (
    show_dialog,
    DialogIcon,
    SudoCommandThread,
    LogLevel,
    DebSoftwareApp,
    LogLevel_To_DialogIcon
)

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib

# i18n: http://docs.python.org/3/library/gettext.html
_ = gettext.translation('solydxk-system', fallback=True).gettext

# Packages that need to be localized
LOCALIZE_PACKAGES = [
    'libreoffice',
    'libreoffice-help',
    'libreoffice-spellcheck',
    'firefox-esr',
    'firefox',
    'thunderbird',
    'hunspell',
    'hunspell-dictionary'
]


class TabLocales:
    """
    Locales tab for SolydXK System
    """
    def __init__(self, app: DebSoftwareApp) -> None:
        self.app: DebSoftwareApp = app
        self.continent: str = None
        self.continent_org: str = None
        self.city: str = None
        self.city_org: str = None
        self._cache = apt.Cache()

    def setup_locales_tab(self) -> None:
        """
        Set up the localize tab.
        """
        # Reset the progress bar
        self.app.progressbar.set_fraction(0)

        # Check if the treeview is already set up
        if self.app.treeview_locales.get_model():
            return

        # Create a ListStore with columns:
        # Installed (bool), Locale (str), Language (str), Default (bool)
        store = Gtk.ListStore(bool, str, str, bool)
        self.app.treeview_locales.set_model(store)

        # Add columns
        render_check = Gtk.CellRendererToggle()
        render_check.connect("toggled", self.on_installed_toggled, store)
        col_installed = Gtk.TreeViewColumn(_("Installed"), render_check, active=0)
        col_installed.set_expand(True)
        col_installed.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
        self.app.treeview_locales.append_column(col_installed)

        render_text = Gtk.CellRendererText()
        col_locale = Gtk.TreeViewColumn(_("Locale"), render_text, text=1)
        col_locale.set_expand(True)
        col_locale.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
        self.app.treeview_locales.append_column(col_locale)

        render_text = Gtk.CellRendererText()
        col_lang = Gtk.TreeViewColumn(_("Language"), render_text, text=2)
        col_lang.set_expand(True)
        col_lang.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
        self.app.treeview_locales.append_column(col_lang)

        render_check = Gtk.CellRendererToggle()
        render_check.connect("toggled", self.on_default_toggled, store)
        col_default = Gtk.TreeViewColumn(_("Default"), render_check, active=3)
        col_default.set_expand(True)
        col_default.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
        self.app.treeview_locales.append_column(col_default)

        # Setup locales
        localization = Localize()
        locales = localization.get_locales()
        if not locales:
            self.app.logging.error("No locales found")
        for key, info in locales.items():
            store.append([info['installed'], key, info['name'], info['default']])

        # Setup timezones
        timezones = localization.get_timezones()
        if not timezones:
            self.app.logging.error("No timezones found")

        # Fill the continent dropdown
        self.app.dd_continent.set_model(Gtk.StringList.new(list(timezones.keys())))
        # Set an empty model for the city dropdown
        # (it will be filled later when the continent is selected)
        self.app.dd_city.set_model(Gtk.StringList.new([]))

        # Get current timezone and save them for later
        self.continent, self.city = localization.get_current_timezone()
        self.continent_org = self.continent
        self.city_org = self.city

        # Select the current continent
        for i, continent in enumerate(timezones.keys()):
            if continent == self.continent:
                self.app.dd_continent.set_selected(i)
                self.app.logging.debug("Select current continent: %s", continent)
                break

        # Connect signal for continent dropdown
        self.app.dd_continent.connect(
            "notify::selected-item",
            lambda dropdown, pspec: self.on_continent_changed(dropdown,
            self.app.dd_city,
            timezones)
        )

        # Connect signal for city dropdown
        self.app.dd_city.connect(
            "notify::selected-item",
            lambda dropdown, pspec: self.on_city_changed(dropdown)
        )

        # Fill the city dropdown and select the current city
        self.on_continent_changed(self.app.dd_continent,
                                  self.app.dd_city,
                                  timezones)

        # Add save button
        self.app.save_locale_button.connect("clicked",
                                            self.on_save_locale_clicked,
                                            store,
                                            locales)

    def on_continent_changed(self,
                             dropdown: Gtk.DropDown,
                             dd_city: Gtk.DropDown,
                             timezones: dict) -> None:
        """
        Handle the change event for the continent dropdown.
        """
        self.continent = dropdown.get_selected_item().get_string()

        city_model = Gtk.StringList.new()
        for city in timezones[self.continent]:
            city_model.append(city)
        dd_city.set_model(city_model)

        # Select the current city if it exists
        for i, city in enumerate(timezones[self.continent]):
            if city == self.city:
                dd_city.set_selected(i)
                self.app.logging.debug("Select current city: %s", city)
                break

    def on_city_changed(self, dropdown: Gtk.DropDown) -> None:
        """
        Handle the change event for the city dropdown.
        """
        self.city = dropdown.get_selected_item().get_string()

    @staticmethod
    def on_installed_toggled(_renderer: Gtk.CellRendererToggle,
                             path: int,
                             store: Gtk.ListStore) -> None:
        """
        Handle the click event for the installed check button.
        """
        store[path][0] = not store[path][0]

    @staticmethod
    def on_default_toggled(_renderer: Gtk.CellRendererToggle,
                           path: int,
                           store: Gtk.ListStore) -> None:
        """
        Handle the click event for the default check button.
        """
        # Deselect the current default
        for row in store:
            if row[3]:
                row[3] = False
                break
        # Select the new default
        store[path][3] = True
        # Ensure it's installed
        store[path][0] = True

    @staticmethod
    def _is_metapackage(pkg) -> bool:
        rec = pkg.candidate.record
        if rec.get("Section", "") == "metapackages":
            return True
        if rec.get("Installed-Size", "0") == "0":
            return True
        return False

    def _is_installed(self, package_name: str) -> bool:
        pkg = self._cache.get(package_name)

        # Exact match exists
        if pkg is not None:
            if pkg.is_installed:
                return True

            # If it's a metapackage (installed or not), allow prefix matching
            if self._is_metapackage(pkg):
                regex = re.compile(rf"^{re.escape(package_name)}(-|$)")
                for p in self._cache:
                    if regex.match(p.name) and p.is_installed:
                        return True

            # Not installed and not a metapackage
            return False

        # Package does not exist
        return False

    def _is_available(self, package_name: str) -> bool:
        return package_name in self._cache

    def _get_localize_packages(self, store: Gtk.ListStore, locales: dict) -> Optional[dict]:
        """
        Get the list of packages needed for localization.
        """
        kde_plasma = os.path.exists("/etc/xdg/plasma-workspace") or \
                     any("plasma-workspace" in line for line in
                     os.popen("dpkg -l").read().splitlines())

        installed_locales = {}
        for row in store:
            installed, locale, _language, is_default = row
            if (locales.get(locale, {}).get('installed') != installed or
                locales.get(locale, {}).get('default') != is_default):
                installed_locales[locale] = {'installed': installed,
                                             'default': is_default,
                                             'packages': []}

        if not installed_locales and \
        self.continent == self.continent_org and \
        self.city == self.city_org:
            show_dialog(_("Localisation"),
                        _("Locales and timezones were not changed."),
                        self.app.window)
            return None

        for package in LOCALIZE_PACKAGES:
            if not self._is_installed(package):
                continue
            for locale, value in installed_locales.items():
                lan = locale.lower().replace('_', '-')
                if self._is_available(f"{package}-l10n-{lan}") and \
                    not self._is_installed(f"{package}-l10n-{lan}"):
                    value["packages"].append(f"{package}-l10n-{lan}")
                else:
                    lan = lan.split('-')[0]
                    if self._is_available(f"{package}-l10n-{lan}") and \
                        not self._is_installed(f"{package}-l10n-{lan}"):
                        value["packages"].append(f"{package}-l10n-{lan}")

        # Add packages from the locale files
        for locale, value in installed_locales.items():
            packages = []
            locale_path = os.path.join(self.app.localize_dir, locale)
            if os.path.exists(locale_path):
                with open(file=locale_path, mode='r', encoding='utf-8') as f:
                    for line in f.readlines():
                        if line.startswith('all='):
                            packages = line.split('=')[1].strip().split()
                        if kde_plasma and line.startswith('qt='):
                            packages += line.split('=')[1].strip().split()

            # Check if the packages are already installed
            for package in packages:
                if self._is_installed(package):
                    packages.remove(package)

            value["packages"] += packages

        return installed_locales

    def on_save_locale_clicked(self,
                               _button: Gtk.Button,
                               store: Gtk.ListStore,
                               locales: dict) -> None:
        """
        Handle the click event for the save button.
        """
        message = ''

        installed_locales = self._get_localize_packages(store, locales)
        if installed_locales is None:
            return

        # Write to a temporary JSON file
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
            json.dump(installed_locales, f, indent=2)
            json_file = f.name

        # Get the selected timezone
        timezone = f"{self.continent}/{self.city}" if self.continent and self.city else ''
        self.app.logging.debug("Selected timezone: %s", timezone)

        # pkexec will prompt for root password via polkit
        command = ["pkexec",
                   "--disable-internal-agent",
                   "python3",
                   os.path.join(self.app.script_dir, "localize_sudo.py"),
                   json_file,
                   timezone]

        result_queue = queue.Queue()
        thread = SudoCommandThread(command,
                                   _("Localisation"),
                                   result_queue,
                                   self.app.logging)
        thread.start()

        # Use a timeout to update the progress bar
        def update_progress():
            nonlocal message
            if not result_queue.empty():
                dict_info = result_queue.get()
                level = dict_info['level']
                message = dict_info.get('message')

                if level == LogLevel.ERROR:
                    # An error occurred
                    show_dialog(_("Localisation"),
                                message,
                                self.app.window,
                                DialogIcon.ERROR)
                    self.app.progressbar.set_fraction(0)
                    self.app.progressbar_label.set_text("")
                    return False

                self.app.progressbar.set_fraction(dict_info['progress'] / 100)
                self.app.progressbar_label.set_text(message)

            if not thread.is_alive() and result_queue.empty():
                # Clean up the temporary file
                if os.path.exists(json_file):
                    os.remove(json_file)

                # Show dialog
                if level == LogLevel.SUCCESS:
                    message = _("Repositories have been saved successfully.")
                show_dialog(_("Repositories"),
                            message,
                            self.app.window,
                            LogLevel_To_DialogIcon.get(level, DialogIcon.INFO))
                self.app.progressbar_label.set_text("")

                # All done
                return False

            # Keep going
            return True

        # Add the timeout callback (runs every 100ms)
        GLib.timeout_add(100, update_progress)
