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

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

import os
import gettext
import queue
import tempfile
import json
import copy
from typing import Optional
from utils import (
    show_dialog,
    DialogIcon,
    get_domain_from_uri,
    is_dpkg_locked,
    SudoCommandThread,
    LogLevel,
    DebSoftwareApp,
    LogLevel_To_DialogIcon
)

from repositories import (
    SolydAptSources,
    Repositories,
    Deb822SourceEntry
)

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


class TabRepositories:
    """
    Repositories tab for SolydXK System
    """
    def __init__(self, app: DebSoftwareApp) -> None:
        self.app: DebSoftwareApp = app
        self.apt_sources: SolydAptSources = SolydAptSources(self.app)
        self.solydxk_checkboxes: list[Gtk.CheckButton] = []
        self.solydxk_max_checked: int = 1
        self.solydxk_source: Optional[Deb822SourceEntry] = None
        self.debian_checkboxes: list[Gtk.CheckButton] = []
        self.debian_max_checked: int = 1
        self.debian_source: Optional[Deb822SourceEntry] = None

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

        # Check if the listboxes are already set up
        if self.app.listbox_repo_solydxk.get_row_at_index(0) is not None:
            return

        # Fill the SolydXK listbox
        self._fill_listbox(Repositories.SOLYDXK)

        # Fill the Debian listbox
        self._fill_listbox(Repositories.DEBIAN)

        # Fill other repo listbox
        self._fill_listbox(Repositories.OTHER)

        # Add save buttons
        self.app.save_sources_button.connect("clicked",
                                                     self.save_source_list)

    def save_source_list(self, _button: Gtk.Button) -> None:
        """
        Save the modified sources.list file.

        Args:
            modified_sources_list (list): The list of modified Deb822SourceEntry objects.
        """
        message = ''
        if is_dpkg_locked():
            self.app.logging.error("dpkg is locked by another process.")
            return

        # Get the modified sources
        solydxk_sources = self.get_changed_sources(Repositories.SOLYDXK)
        debian_sources = self.get_changed_sources(Repositories.DEBIAN)
        other_sources = self.get_changed_sources(Repositories.OTHER)

        # Merge the lists
        modified_sources_list = solydxk_sources + debian_sources + other_sources

        # Save each entry's line and file to a JSON file
        entries_data = []
        processed_files = set()  # Track processed files to avoid duplicates

        for entry_dict in modified_sources_list:
            old_entry = entry_dict['old']
            new_entry = entry_dict['new']
            sections = []

            # Track if we've processed this file already
            if new_entry.file not in processed_files:
                for source_entry in self.apt_sources.sources.list:
                    if source_entry == old_entry:
                        # Update the old entry
                        source_entry.disabled = new_entry.disabled
                        source_entry.uri = new_entry.uri
                        source_entry.suites = new_entry.suites
                        sections.append(self.apt_sources.get_source_section(source_entry))
                        self.app.logging.debug(f"Updated in {old_entry.file}, "
                                                       f"source: {source_entry.uri}")
                    elif source_entry.file == new_entry.file:
                        # Make sure the source_entry has a section
                        source_section = self.apt_sources.get_source_section(source_entry)
                        if source_section:
                            # Add other sources with the same file
                            sections.append(source_section)
                            self.app.logging.debug(f"Added in {old_entry.file}, "
                                                           f"source: {source_entry.uri}")

                # Mark this file as processed
                processed_files.add(new_entry.file)

            # Save the section and file path for the JSON file
            entries_data.append({
                "section": '\n'.join(sections),
                "file": new_entry.file
            })

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

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

        result_queue = queue.Queue()
        thread = SudoCommandThread(command,
                                   _("Repositories"),
                                   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(_("Repositories"),
                                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)

    # def _repo_changed(self, repository: Repositories) -> None:
    def get_changed_sources(self, repository: Repositories) -> list[dict]:
        """ 
        Check if the selected repository has changed.

        Args:
            repository (Repositories): The repository that you want to check.
        """
        if repository == Repositories.OTHER:
            return self._get_other_repos()

        source = self.solydxk_source if repository == Repositories.SOLYDXK else self.debian_source

        if source is None:
            # No source was configured
            return []

        new_source = None
        source_has_backports = False
        backports = False

        if repository == Repositories.DEBIAN:
            source_has_backports = self.source_has_backports(source)
            backports = self.app.chk_backports.get_active()
            row: Gtk.ListBoxRow | None = self.app.listbox_repo_debian.get_first_child()
        else:
            row: Gtk.ListBoxRow | None = self.app.listbox_repo_solydxk.get_first_child()

        while row is not None:
            # Get the checkbox
            checkbox: Gtk.CheckButton = row.get_child()

            if not checkbox.get_active():
                row = row.get_next_sibling()
                continue

            if checkbox.uri == source.uri and \
               backports == source_has_backports:
                # Nothing changed
                self.app.logging.debug(f"No changes were made to {checkbox.uri}")
                return []

            # Changes were made: create a copy of the start repo
            new_source = copy.deepcopy(source)
            if checkbox.uri != source.uri:
                # Another mirror was selected
                self.app.logging.debug("Switch Debian mirror repository: %s", checkbox.uri)
                new_source.uri = checkbox.uri

            if backports != source_has_backports:
                # Check backports
                base_suite = ''
                backports_suite = ''
                for suite in source.suites:
                    if not base_suite and not '-' in suite:
                        base_suite = suite
                    elif not backports_suite and 'backports' in suite:
                        backports_suite = suite

                # Add or remove backports suite
                suites = list(new_source.suites)
                if backports:
                    backports_suite = f"{base_suite}-backports"
                    self.app.logging.debug("Add backports: %s", backports_suite)
                    suites.append(backports_suite)
                    new_source.suites = suites
                else:
                    self.app.logging.debug("Remove backports: %s", backports_suite)
                    suites.remove(backports_suite)
                    new_source.suites = suites

            # There Can Be Only One (Highlander reference)
            return [{'old': source, 'new': new_source}]

        # Nothing changed
        return []

    def _get_other_repos(self) -> list[dict]:
        """
        Get a list of changed repositories other than SolydXK or Debian.
        """
        modified_sources_list = []
        row: Gtk.ListBoxRow | None = self.app.listbox_repo_other.get_first_child()
        while row is not None:
            # Get the checkbox
            checkbox: Gtk.CheckButton = row.get_child()

            # Other repositories can only be enabled/disabled
            for source_entry in self.apt_sources.other_sources:
                if checkbox.uri == source_entry.uri:
                    if checkbox.get_active() != source_entry.disabled:
                        # Nothing changed
                        break

                    # Other repositories can only be enabled or disabled
                    enable_string = "Enabled" if checkbox.get_active() else "Disabled"
                    self.app.logging.debug("%s repository: %s", enable_string, checkbox.uri)
                    new_source = copy.deepcopy(source_entry)
                    new_source.disabled = not checkbox.get_active()
                    modified_sources_list.append({'old': source_entry, 'new': new_source})
                    break

            # Get next row
            row = row.get_next_sibling()

        return modified_sources_list

    def on_checkbox_toggled(self,
                            checkbox: Gtk.CheckButton,
                            checked_list: list[Gtk.CheckButton],
                            repository: str):
        """
        Handle the checkbox toggle event for SolydXK and Debian repositories.

        Args:
            checkbox (Gtk.CheckButton): The checkbox that was toggled.
            checked_list (list): The list of checked checkboxes.
            repository (str): The repository name.
        """
        repository = repository.lower()
        if repository not in Repositories.SOLYDXK and \
        repository not in Repositories.DEBIAN:
            return

        max_checked = self.solydxk_max_checked if repository in Repositories.SOLYDXK \
                      else self.debian_max_checked

        # Block the handler to prevent recursion
        checkbox.handler_block(checkbox.handler_id)

        if not checkbox.get_active() and len(checked_list) == 1:
            # Prevent unchecking the last checkbox
            checkbox.set_active(True)
        else:
            if checkbox.get_active():
                if len(checked_list) >= max_checked:
                    # Uncheck the oldest checked checkbox
                    oldest = checked_list.pop(0)
                    oldest.handler_block(oldest.handler_id)
                    oldest.set_active(False)
                    oldest.handler_unblock(oldest.handler_id)
                checked_list.append(checkbox)
            else:
                if checkbox in checked_list:
                    checked_list.remove(checkbox)

        # Unblock the handler
        checkbox.handler_unblock(checkbox.handler_id)

    @staticmethod
    def source_has_backports(source_entry) -> bool:
        """
        Check if the source entry has a backports suite.

        Args:
            source_entry (Deb822SourceEntry): The source entry to check.

        Returns:
            bool: True if the source entry has a backports suite, False otherwise.
        """
        if source_entry is None:
            return False

        for suite in source_entry.suites:
            if 'backports' in suite:
                return True
        return False

    def _fill_listbox(self,
                      repository: Repositories) -> None:
        """
        Fill the listbox with the given data.

        Args:
            listbox (Gtk.ListBox): The listbox to fill.
            repository (Repositories): The repository to fill the listbox with.
        """
        listbox = self.app.listbox_repo_other
        mirrors = []
        sources = self.apt_sources.other_sources
        if repository == Repositories.SOLYDXK:
            listbox = self.app.listbox_repo_solydxk
            mirrors = self.apt_sources.solydxk_mirrors
            sources = self.apt_sources.solydxk_sources
        elif repository == Repositories.DEBIAN:
            listbox = self.app.listbox_repo_debian
            mirrors = self.apt_sources.debian_mirrors
            sources = self.apt_sources.debian_sources

        if mirrors:
            # SolydXK or Debian repositories
            for country, mirror_list in mirrors.items():
                for mirror in mirror_list:
                    self.app.logging.debug("Add %s mirror: %s, %s",
                                                   repository.value,
                                                   country,
                                                   mirror.get('uri'))

                    # Check if the mirror is enabled
                    enabled = False
                    for source_entry in sources:
                        clean_url = mirror.get('uri').replace("https://", "").replace("http://", "").rstrip("/")
                        if clean_url in source_entry.uri and not source_entry.disabled:
                            enabled = True

                            # Save the current enabled repository
                            if repository == Repositories.SOLYDXK:
                                self.solydxk_source = source_entry
                            elif repository == Repositories.DEBIAN:
                                self.debian_source = source_entry

                            # Check if the "backports" suite is enabled
                            if repository == Repositories.DEBIAN:
                                self.app.chk_backports.set_active(
                                    self.source_has_backports(source_entry)
                                )
                            break

                    # Create a row for each hardware entry
                    row = Gtk.ListBoxRow()
                    # Add checkbox
                    checkbox = Gtk.CheckButton()
                    checkbox.set_active(enabled)
                    checkbox.uri = mirror.get('uri')
                    checkbox.set_label(country)
                    if country is not None:
                        checkbox.set_tooltip_text(mirror.get('domain'))
                    if repository is not None:
                        save_array = self.solydxk_checkboxes if repository == Repositories.SOLYDXK \
                                     else self.debian_checkboxes
                        handler_id = checkbox.connect("toggled", self.on_checkbox_toggled,
                                                      save_array, repository)
                        checkbox.handler_id = handler_id  # Store the handler_id
                        checkbox.handler_block(handler_id)  # Block the handler
                        # Set initial state without triggering the handler
                        checkbox.set_active(enabled)
                        checkbox.handler_unblock(handler_id)  # Unblock the handler
                    row.set_child(checkbox)
                    # Add the row to the listbox
                    listbox.append(row)

                    if enabled:
                        # Save the checked checkbox to keep history
                        if repository == Repositories.SOLYDXK:
                            self.solydxk_checkboxes.append(checkbox)
                        elif repository == Repositories.DEBIAN:
                            self.debian_checkboxes.append(checkbox)

        elif sources:
            # Other repositories
            for source_entry in sources:
                domain = get_domain_from_uri(source_entry.uri)
                self.app.logging.debug("Add other repository: %s, %s",
                                               domain,
                                               source_entry.uri)

                # Create a row for each hardware entry
                row = Gtk.ListBoxRow()
                # Add checkbox
                checkbox = Gtk.CheckButton()
                checkbox.set_active(not source_entry.disabled)
                checkbox.uri = source_entry.uri
                checkbox.set_label(domain)
                row.set_child(checkbox)
                # Add the row to the listbox
                listbox.append(row)
