# Part of Carburetor project
# Released under GPLv3+ License
# Danial Behzadi <dani.behzi@ubuntu.com>, 2020-2024.

"""
handlers for ui events
"""

from gi.repository import Adw, Gio, GLib, GObject, Gtk
from tractor import control, bridges, db

from . import config, ui


class Country(GObject.Object):
    """
    prepare factory for countries
    """
    __gtype_name__ = "Country"

    def __init__(self, alpha_2: str, label: str):
        super().__init__()
        self._alpha_2 = alpha_2
        self._label = label

    @GObject.Property
    def alpha_2(self) -> str:
        """
        return country alpha_2
        """
        return self._alpha_2

    @GObject.Property
    def label(self) -> str:
        """
        return country label
        """
        return self._label


class BridgeType(GObject.Object):
    """
    Prepare factory for bridge types
    """
    __gtype_name__ = "BridgeType"

    def __init__(self, type_id: int, type_name: str):
        super().__init__()
        self._bridgetype_id = type_id
        self._bridgetype_name = type_name

    @GObject.Property
    def type_id(self) -> int:
        """
        return ids
        """
        return self._bridgetype_id

    @GObject.Property
    def type_name(self) -> str:
        """
        return names
        """
        return self._bridgetype_name


def on_mainpage_realize(window):
    """
    First setups
    """
    if window:
        app = window.get_root().get_application()
        ui.set_run_status(app)


def on_factory_setup(factory, list_item):
    """
    set labels for dropdown
    """
    del factory
    label = Gtk.Label()
    list_item.set_child(label)


def on_factory_bind(factory, list_item):
    """
    set country names as labels
    """
    del factory
    label = list_item.get_child()
    country = list_item.get_item()
    label.set_text(country.label)


def on_exitcountry_change(*argv):
    """
    set exit country in dconf
    """
    combo = argv[0]
    country = combo.get_selected_item()
    country_code_entry_row = ui.get("CountryCode")
    country_code_entry_row.set_sensitive(False)
    alpha_2 = country.alpha_2
    db.set_val("exit-node", alpha_2)
    if country.label == _("Other (Manual)"):
        country_code_entry_row.set_sensitive(True)


def on_exitcountry_realize(combo):
    """
    sets up exit node comborow
    """
    countries = list(config.NODES.keys())
    labels = list(config.NODES.values())

    node = db.get_val("exit-node")
    sorted_labels = sorted(labels)
    if node == "ww":
        index = 0
        manual = ""
    else:
        index = len(countries) + 1
        manual = node
        code_flag_map = {c.alpha_2: c.flag for c in countries}
        flag = code_flag_map.get(node)
        if flag:
            for i, label in enumerate(sorted_labels):
                if flag in label:
                    index = i + 1
                    manual = ""
                    break

    # Define and populate the model
    model = Gio.ListStore(item_type=Country)
    model.append(Country(alpha_2="ww", label=_("Auto (Best)")))
    label_contry_map = dict(zip(labels, countries))
    for label in sorted_labels:
        country = label_contry_map[label]
        model.append(Country(alpha_2=country.alpha_2, label=label))
    model.append(Country(alpha_2=manual, label=_("Other (Manual)")))

    combo.set_model(model)
    combo.set_selected(index)


def on_country_code_realize(entry):
    """
    bind country code
    """
    config.DCONF.bind(
        "exit-node",
        entry,
        "text",
        Gio.SettingsBindFlags.DEFAULT,
    )
    entry.set_sensitive(False)
    combo = ui.get("ExitCountryCombo")
    country = combo.get_selected_item()
    if country.label == _("Other (Manual)"):
        entry.set_sensitive(True)


def on_actionacceptconnection_realize(switch):
    """
    bind accept-connection
    """
    config.DCONF.bind(
        "accept-connection",
        switch,
        "active",
        Gio.SettingsBindFlags.DEFAULT,
    )


def on_fascist_realize(switch):
    """
    bind fascist-firewall
    """
    config.DCONF.bind(
        "fascist-firewall",
        switch,
        "active",
        Gio.SettingsBindFlags.DEFAULT,
    )


def on_actionsocksport_realize(spin):
    """
    bind socks-port
    """
    port = db.get_val("socks-port")
    spin.set_text(str(port))
    config.DCONF.bind(
        "socks-port", spin, "value", Gio.SettingsBindFlags.DEFAULT
    )


def on_actiondnsport_realize(spin):
    """
    bind socks-port
    """
    port = db.get_val("dns-port")
    spin.set_text(str(port))
    config.DCONF.bind("dns-port", spin, "value", Gio.SettingsBindFlags.DEFAULT)


def on_actionhttpport_realize(spin):
    """
    bind http-port
    """
    port = db.get_val("http-port")
    spin.set_text(str(port))
    config.DCONF.bind(
        "http-port", spin, "value", Gio.SettingsBindFlags.DEFAULT
    )


def on_bt_factory_setup(factory, list_item):
    """
    set labels for dropdown
    """
    del factory
    label = Gtk.Label()
    list_item.set_child(label)


def on_bt_factory_bind(factory, list_item):
    """
    set bridgetype names as labels
    """
    del factory
    label = list_item.get_child()
    bridge_type = list_item.get_item()
    label.set_text(bridge_type.type_name)


def on_bridgetypecombo_realize(combo):
    """
    create and set model for bridge type
    """
    bridgetype = db.get_val("bridge-type")
    # Define and populate the model
    model = Gio.ListStore(item_type=BridgeType)
    model.append(BridgeType(type_id="none", type_name=_("None")))
    model.append(BridgeType(type_id="vanilla", type_name=_("Vanilla")))
    model.append(BridgeType(type_id="obfs4", type_name=_("Obfuscated")))
    model.append(BridgeType(type_id="snowflake", type_name=_("Snowflake")))
    model.append(BridgeType(type_id="conjure", type_name=_("Conjure")))
    combo.set_model(model)
    types = ["none", "vanilla", "obfs4", "snowflake", "conjure"]
    combo.set_selected(types.index(bridgetype))


def on_bridgetype_change(combo, _):
    """
    set bridge type in dconf
    """
    bridgetype = combo.get_selected_item()
    type_id = bridgetype.type_id
    db.set_val("bridge-type", type_id)
    ui.set_pluginrow_sensivity()
    ui.setup_pluginbutton()


def on_pluginrow_realize(row):
    """
    set row sensitive if a pluggable transport is set
    """
    ui.set_pluginrow_sensivity(row)


def on_pluginbutton_realize(button):
    """
    setup plugin button
    """
    ui.setup_pluginbutton(button)


def on_pluginbutton_clicked(button):
    """
    open file chooser
    """
    button.chooser.open(callback=on_pluginchooser_response)


def on_pluginchooser_response(chooser, task):
    """
    get plugin file and update plugin button
    """
    try:
        file = chooser.open_finish(task)
    except GLib.GError:
        # gtk-dialog-error-quark: Dismissed by user
        pass
    else:
        db.set_val("pluggable-transport", file.get_path())
        ui.setup_pluginbutton()


def set_title(transport: str) -> str:
    """
    return human-readable title for transport types
    """
    match transport:
        case "vanilla":
            return _("Vanilla")
        case (
            "meek_lite"
            | "obfs2"
            | "obfs3"
            | "obfs4"
            | "scramblesuit"
            | "webtunnel"
        ):
            return _("Obfuscated")
        case "snowflake":
            return _("Snowflake")
        case "conjure":
            return _("Conjure")


def add_bridge_row(line: str, group=None):
    """
    add bridge line to bridges group
    """
    parsed = bridges.parse_bridge_line(line)
    if parsed["transport"]:
        suffix = _("Connected") if line in control.get_bridge() else ""
        action_row = Adw.ActionRow()
        if suffix:
            title = (
                f"{set_title(parsed['transport'])} "
                + f"<span foreground='#B765F4'>   ✔ {suffix}</span>"
            )
            action_row_style = action_row.get_style_context()
            action_row_style.add_class("suggested-action")
        else:
            title = set_title(parsed["transport"])
        action_row.set_title(title)
        action_row.set_subtitle(
            " ".join(bridges.create_emoji(line)) + "\t" + parsed["addr"]
        )
        action_row.set_subtitle_lines(1)
        button = Gtk.Button()
        button.set_valign(Gtk.Align.CENTER)
        button.set_icon_name("list-remove-symbolic")
        button.set_tooltip_text(_("Remove"))
        button.connect("clicked", on_remove_bridge, line)
        button_style = button.get_style_context()
        button_style.add_class("circular")
        button_style.add_class("destructive-action")
        action_row.add_suffix(button)
        if not group:
            group = ui.get("BridgesGroups")
        rows = group.get_first_child().get_last_child().get_first_child()
        add_button = list(rows)[-1]
        group.remove(add_button)
        group.add(action_row)
        insert_add_button(group)


def insert_add_button(group):
    """
    append add button to the end of bridges list
    """
    button = Adw.ButtonRow()
    button.set_start_icon_name("list-add-symbolic")
    button.set_action_name("app.open-add")
    button.set_title(_("_Add"))
    button.set_use_underline(True)
    group.add(button)


def on_remove_bridge(button, item: str):
    """
    remove a bridge from list of bridges
    """
    bridges_file = bridges.get_file()
    with open(bridges_file, "r", encoding="utf-8") as file:
        lines = file.readlines()
    lines = [line for line in lines if line.strip() != item]
    with open(bridges_file, "w", encoding="utf-8") as file:
        file.writelines(lines)
    row = ui.find_parent(button, Adw.ActionRow)
    group = row.get_parent()
    group.remove(row)


def on_bridges_realize(group):
    """
    empty the group and populate it with bridges
    """
    rows = group.get_first_child().get_last_child().get_first_child()
    for row in list(rows)[:-1]:
        group.remove(row)
    bridges_file = bridges.get_file()
    with open(bridges_file, "r", encoding="utf-8") as file:
        text = file.read()
    for line in text.split("\n"):
        add_bridge_row(line, group)


def on_bridge_entry_change(entry):
    """
    checks if the line is a valid bridge to show apply button
    """
    button = ui.get("BridgeApply")
    button.set_sensitive(False)
    label = ui.get("DuplicateLabel")
    label.set_label("")
    line = entry.get_text()
    parsed = bridges.parse_bridge_line(line)
    if parsed["transport"]:
        bridges_file = bridges.get_file()
        with open(bridges_file, "r", encoding="utf-8") as file:
            current = file.read()
        if line in current:
            label.set_label(_("Duplicate"))
        else:
            button.set_sensitive(True)


def on_save_bridge(_button):
    """
    save the bridge in Bridges file
    """
    entry = ui.get("BridgeEntry")
    line = entry.get_text()
    bridges_file = bridges.get_file()
    with open(bridges_file, "a", encoding="utf-8") as file:
        file.write(f"{line}\n")
    entry.set_text("")
    dialog = ui.get("AddBridgeDialog")
    dialog.close()
    add_bridge_row(line)
