#!/usr/bin/python3


from __future__ import annotations

import ast
import logging
import os
import re
import subprocess
from pathlib import Path
from typing import Any, NoReturn, Union

import tomlkit

from slpkg.config import config_load
from slpkg.dialog_box import DialogBox
from slpkg.errors import Errors

logger = logging.getLogger(__name__)


class FormConfigs:  # pylint: disable=[R0902]
    """Edit slpkg.toml config file with dialog utility."""

    def __init__(self, options: dict[str, bool]) -> None:
        logger.debug("Initializing FormConfigs module with options: %s", options)
        self.dialog = config_load.dialog
        self.etc_path = config_load.etc_path
        self.prog_name = config_load.prog_name
        self.config = config_load.config
        self.editor = config_load.editor

        self.dialogbox = DialogBox()
        self.errors = Errors()

        self.original_configs: dict[str, dict[str, Any]] = {}
        self.config_file: Path = self.etc_path / f"{self.prog_name}.toml"
        self.option_for_edit: bool = options.get("option_edit", False)
        logger.debug(
            "FormConfigs initialized. Dialog enabled: %s, Option for direct edit: %s, Config file: %s, Config editor: %s",
            self.dialog,
            self.option_for_edit,
            self.config_file,
            self.editor,
        )

    def run(self) -> None:
        """Choose tool to edit the configuration file."""
        logger.info("Starting configuration file editing process.")
        if self.dialog:
            logger.debug("Dialog utility is enabled.")
            if self.option_for_edit:
                logger.info("Option --edit is enabled. Calling external editor first.")
                self.editor_config()
            logger.info("Calling dialog_edit for interactive form.")
            self.dialog_edit()
        else:
            logger.info("Dialog utility is disabled. Calling external editor only.")
            self.editor_config()

    def editor_config(self) -> NoReturn:
        """Edit slpkg.toml configuration file with system EDITOR"""
        editor = self.editor
        if not editor:
            editor = os.environ.get("EDITOR", "")
        if not editor:
            logger.error("No editor configured or found in environment.")
            self.errors.message("No editor configured or found. Cannot open editor.", exit_status=1)

        command: list[Union[str, Path]] = [editor, self.config_file]
        logger.info("Opening configuration file '%s' with external editor: '%s'", self.config_file, editor)
        try:
            subprocess.run(command, check=True)
            logger.info("External editor exited successfully.")
        except subprocess.CalledProcessError as e:
            logger.error("External editor command failed with exit code %s: %s", e.returncode, e, exc_info=True)
            self.errors.message(f"External editor failed with exit code {e.returncode}", exit_status=e.returncode)
        except FileNotFoundError:
            logger.error(
                "External editor '%s' not found. Please ensure it is installed and in your PATH.", editor, exc_info=True
            )
            self.errors.message(f"External editor '{editor}' not found.", exit_status=1)
        except Exception as e:  # pylint: disable=[W0718]
            logger.critical("An unexpected error occurred while opening external editor: %s", e, exc_info=True)
            self.errors.message(f"An unexpected error occurred with editor: {e}", exit_status=1)

        raise SystemExit(0)

    def dialog_edit(self) -> None:
        """Read and write the configuration file using dialog form."""
        logger.info("Preparing dialog form for editing configuration file: %s", self.config_file)
        # elements format: (tag, row, col, default_value, field_row, field_col, field_length, input_max_length, help_text, help_text_label)
        elements: list[tuple[Any, ...]] = []
        height: int = 0
        width: int = 0
        form_height: int = 0
        text: str = f"Edit the configuration file: {self.config_file}"
        title: str = " Configuration File "

        # Use the actual keys from self.config (flattened by config_load)
        for i, (key, value) in enumerate(self.config.items(), start=1):
            display_value = str(value)
            if isinstance(value, bool):
                display_value = "true" if value else "false"

            elements.append((key, i, 1, display_value, i, 21, 47, 200, "0x0", f"Config: {key} = {display_value}"))
            logger.debug("Added dialog element for key '%s' with value '%s'.", key, display_value)

        logger.debug("Calling dialogbox.mixedform with %d elements.", len(elements))
        code, tags = self.dialogbox.mixedform(text, title, elements, height, width, form_height)

        print(config_load.clear_screen, end="", flush=True)
        logger.debug("Dialogbox exited with code: '%s', returned tags: %s", code, tags)

        if code == "help":
            logger.info("User requested help from dialog. Displaying textbox with config file content.")
            self.help()
        elif code == "ok":
            logger.info("User confirmed changes in dialog. Writing new configurations.")
            self.write_configs(tags)
        else:
            logger.info("Dialog was cancelled or returned an unexpected code ('%s'). No changes written.", code)

    def help(self) -> None:
        """Load the configuration file on a text box."""
        logger.info("Displaying help textbox for config file: %s", self.config_file)
        self.dialogbox.textbox(self.config_file, 40, 60)
        logger.debug("Textbox closed. Returning to dialog_edit.")
        self.dialog_edit()

    def read_configs(self) -> None:
        """Read the original config file into self.original_configs."""
        logger.debug("Reading original config file: %s", self.config_file)
        try:
            self.original_configs = tomlkit.parse(self.config_file.read_text(encoding="utf-8"))
            logger.info("Successfully read original config file.")
        except FileNotFoundError:
            logger.error("Original config file '%s' not found during read_configs. Cannot proceed.", self.config_file)
            self.errors.message(f"Configuration file '{self.config_file}' not found.", exit_status=1)
        except Exception as e:  # pylint: disable=[W0718]
            logger.critical("Error reading original config file '%s': %s", self.config_file, e, exc_info=True)
            self.errors.message(f"Error reading configuration file: {e}", exit_status=1)

    def _coerce_value(self, key: str, new_value_str: str) -> Any:
        """Coerce a dialog string value to the appropriate Python type.

        Args:
            key (str): Config key name (used only for error messages).
            new_value_str (str): Raw string value returned by the dialog form.

        Returns:
            Any: Coerced value (bool, int, float, list, or str).
        """
        if new_value_str.lower() == "true":
            return True
        if new_value_str.lower() == "false":
            return False
        if re.match(r"^-?\d+(\.\d+)?$", new_value_str):
            return int(new_value_str) if "." not in new_value_str else float(new_value_str)
        if re.match(r"^\s*\[.*\]\s*$", new_value_str):
            try:
                value = ast.literal_eval(new_value_str)
                if not isinstance(value, list):
                    raise ValueError("Evaluated value is not a list.")
                return value
            except (SyntaxError, ValueError, TypeError) as e:
                logger.error(
                    "Error parsing list value for key '%s': '%s'. Error: %s", key, new_value_str, e, exc_info=True
                )
                self.errors.message(
                    f"Error parsing config file for key '{key}'. Invalid list format: '{new_value_str}'", 1
                )
        return new_value_str

    def _update_section(self, key: str, new_value: Any) -> bool:
        """Find the TOML section that owns key and update its value.

        Args:
            key (str): Config key to update.
            new_value (Any): Coerced value to assign.

        Returns:
            bool: True if the key was found and updated, False otherwise.
        """
        for section_name, section_data in self.original_configs.items():
            if isinstance(section_data, dict) and key in section_data:
                section_data[key] = new_value
                logger.debug("Updated config '%s' in section '%s' to: %s", key, section_name, new_value)
                return True
        return False

    def write_configs(self, tags: list[str]) -> None:
        """Write new configs to the file.

        Args:
            tags (list[str]): User new configs (values from the dialog form).
        """
        logger.info("Writing new configurations to file: %s. Received tags: %s", self.config_file, tags)
        self.read_configs()

        config_keys = list(self.config.keys())

        for i, new_value_str in enumerate(tags):
            if i >= len(config_keys):
                break

            key = config_keys[i]
            logger.debug("Processing config key '%s' with new string value '%s'.", key, new_value_str)

            new_value: Any = self._coerce_value(key, new_value_str)

            if not self._update_section(key, new_value):
                logger.warning("Key '%s' not found in any section of original_configs.", key)

        try:
            self.config_file.write_text(tomlkit.dumps(self.original_configs), encoding="utf-8")
            logger.info("Successfully wrote new configurations to file: %s", self.config_file)
        except OSError as e:
            logger.critical("Failed to write configuration file '%s': %s", self.config_file, e, exc_info=True)
            self.errors.message(f"Error writing configuration file: {e}", exit_status=1)
